Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Important
This feature is in Public Preview. Workspace admins can control access to DataFrame caching on serverless compute from the Previews page. See Manage Azure Databricks previews.
On serverless compute, you can cache a DataFrame with df.cache() or df.persist() to reuse an expensive result in your session. Azure Databricks computes the DataFrame when you call cache() and materializes the result as a snapshot. The DataFrame and the DataFrames you build from it then read the snapshot for the rest of your session instead of recomputing it.
Requirements
- The DataFrame caching on serverless compute preview is enabled for your workspace (enabled by default). Workspace admins can disable it from the Previews page. The preview isn't available in workspaces with the compliance security profile enabled. See Manage workspace-level previews.
- Notebooks and jobs on serverless compute must use environment version 5 or above.
- Databricks Connect for Python must be 18.1.8 or above, and Databricks Connect for Scala must be 18.1.5 or above.
Older clients get the CACHE_REQUIRES_NEWER_CLIENT_ON_SERVERLESS_GC error.
When to use DataFrame caching on serverless compute
Cache a DataFrame on serverless compute when you need to:
- Reuse the result of an expensive join, aggregation, or filter several times in the same session.
- Compute a streaming micro-batch one time in a
foreachBatchfunction, then write it to several sinks. - Train or tune several machine learning models on the same prepared feature data.
- Keep a stable snapshot while you run several steps of an analysis, even if the source tables change.
See Best practices.
Cache DataFrame APIs on serverless compute
The following example caches one day of orders joined with customer data, computes the day's total revenue, appends the revenue by region to a report table, and then releases the cache. Both results read the cached snapshot instead of running the join again.
Python
from pyspark.sql import functions as F
# The DataFrame to cache: one day of orders joined with customer data.
oct_15_orders = (
spark.table("main.sales.orders")
.filter(F.col("order_date") == "2026-10-15")
.join(spark.table("main.sales.customers"), "customer_id")
)
oct_15_orders.cache()
# Both results are built from oct_15_orders, so they read the cached snapshot.
total_revenue = oct_15_orders.agg(F.sum("amount")).first()[0]
print(f"Total revenue on 2026-10-15: {total_revenue}")
revenue_by_region = oct_15_orders.groupBy("order_date", "region").agg(F.sum("amount").alias("revenue"))
revenue_by_region.write.mode("append").saveAsTable("main.reports.revenue_by_region")
# Release the cache and the DataFrame when you no longer need it.
# oct_15_orders and DataFrames built from it can no longer be used.
oct_15_orders.unpersist()
Scala
import org.apache.spark.sql.functions._
// The DataFrame to cache: one day of orders joined with customer data.
val oct15Orders = spark.table("main.sales.orders")
.filter(col("order_date") === "2026-10-15")
.join(spark.table("main.sales.customers"), "customer_id")
oct15Orders.cache()
// Both results are built from oct15Orders, so they read the cached snapshot.
val totalRevenue = oct15Orders.agg(sum("amount")).first().get(0)
println(s"Total revenue on 2026-10-15: $totalRevenue")
val revenueByRegion = oct15Orders.groupBy("order_date", "region").agg(sum("amount").alias("revenue"))
revenueByRegion.write.mode("append").saveAsTable("main.reports.revenue_by_region")
// Release the cache and the DataFrame when you no longer need it.
// oct15Orders and DataFrames built from it can no longer be used.
oct15Orders.unpersist()
In the following API descriptions, df stands for any DataFrame in your session, such as oct_15_orders in the preceding example.
df.cache()
Computes df right away and caches the result as a snapshot for the rest of your session.
- Returns: The same cached DataFrame,
df, now based on the snapshot instead of the computation on its source tables. See the following behavior. - Behavior:
- Runs the DataFrame's query right away and materializes the result as a snapshot before the call returns.
- After the call,
dfand the returned DataFrame (dfitself) are based on the snapshot. They are no longer connected to the computation on the source tables, so they read the snapshot instead of recomputing the query. - DataFrames derived from
df, directly or through other derived DataFrames, read the snapshot automatically instead of running the original query again. See Read the snapshot. - The snapshot is local to your session. Only the session that cached
dfcan read it. See Isolation and privileges. - The snapshot lasts until the session ends, for at most seven days. Azure Databricks then automatically reclaims its storage in the background. Calling
df.unpersist()allows it to reclaim the storage earlier. See Lifecycle and time limits and Storage and cleanup. - If computing
dffails,df.cache()raises the error anddfisn't cached. - Calling
df.cache()on a DataFrame that is already cached does nothing.dfisn't computed again.
The snapshot is fixed after you call df.cache(). Data written afterward to the tables that df reads from, and schema changes to those tables such as added or renamed columns, don't affect it. To recompute from the latest data, build the DataFrame again from the source tables, and cache the new DataFrame if you need to.
Read the snapshot
On serverless compute, cache matching is based on DataFrame derivation. After df.cache(), only the following DataFrames read the snapshot automatically instead of recomputing the query on the source tables:
- The DataFrame you cached,
df. - DataFrames derived from
df, for example withselect(),filter(),groupBy(), orjoin(). - DataFrames further chained from those derived DataFrames.
A new DataFrame that repeats the same query reads the source tables, even though its logic is identical, because it isn't derived from df.
This makes cache matching explicit and under your control: a DataFrame reads cached data only when you derive it from a cached DataFrame. Every other DataFrame reads the source tables.
Python
oct_15_orders = (
spark.table("main.sales.orders")
.filter(F.col("order_date") == "2026-10-15")
.join(spark.table("main.sales.customers"), "customer_id")
)
# The join runs one time, when you call cache().
oct_15_orders.cache()
# Derived from oct_15_orders: read the snapshot, so the join doesn't run again.
large_orders = oct_15_orders.filter(F.col("amount") > 1000)
revenue_by_region = oct_15_orders.groupBy("order_date", "region").agg(F.sum("amount").alias("revenue"))
# Derived from large_orders, which is derived from oct_15_orders: also reads the snapshot.
large_orders_by_region = large_orders.groupBy("region").count()
# Not derived from oct_15_orders: reads the source tables and runs the join again.
same_query = (
spark.table("main.sales.orders")
.filter(F.col("order_date") == "2026-10-15")
.join(spark.table("main.sales.customers"), "customer_id")
)
Scala
val oct15Orders = spark.table("main.sales.orders")
.filter(col("order_date") === "2026-10-15")
.join(spark.table("main.sales.customers"), "customer_id")
// The join runs one time, when you call cache().
oct15Orders.cache()
// Derived from oct15Orders: read the snapshot, so the join doesn't run again.
val largeOrders = oct15Orders.filter(col("amount") > 1000)
val revenueByRegion = oct15Orders.groupBy("order_date", "region").agg(sum("amount").alias("revenue"))
// Derived from largeOrders, which is derived from oct15Orders: also reads the snapshot.
val largeOrdersByRegion = largeOrders.groupBy("region").count()
// Not derived from oct15Orders: reads the source tables and runs the join again.
val sameQuery = spark.table("main.sales.orders")
.filter(col("order_date") === "2026-10-15")
.join(spark.table("main.sales.customers"), "customer_id")
Because df reads the snapshot instead of the DataFrames it was built from, you can't reference the columns of df through those DataFrames. See Limitations.
df.persist(storageLevel)
An alias of df.cache() that accepts a storage level.
- Parameters:
storageLevel(optional): AStorageLevel, such asStorageLevel.DISK_ONLY. serverless compute accepts any storage level and ignores it. The snapshot is stored the same way for every storage level, anddf.storageLevelreturnsStorageLevel.DISK_ONLY.
The return value and behavior are the same as df.cache().
df.unpersist(blocking)
Releases the cached snapshot of df and destroys the DataFrame.
- Parameters:
blocking(optional): Ignored.df.unpersist()returns right away for either value.
- Returns: The same DataFrame,
df, now destroyed. - Behavior:
- Marks the snapshot as ready to be reclaimed. Azure Databricks then deletes the cached data and reclaims the storage in the background.
- Destroys
df:dfand any DataFrames previously derived from it are no longer usable, and any subsequent query on them raisesCACHE_DATAFRAME_ACCESS_AFTER_UNCACHE. If you cache a DataFrame derived fromdfbefore you calldf.unpersist(), that DataFrame stays usable, because it has its own snapshot. - To continue working with the same data source, build a new DataFrame from the original data source, instead of reusing the uncached DataFrame.
- Calling
df.unpersist()on a DataFrame that was never cached, or that has already been uncached, is a no-op.
Call df.unpersist() when you no longer need the cached data, for example when no later step in your notebook or job reads it. This allows Azure Databricks to reclaim the storage earlier. Don't use df.unpersist() to refresh data. To work with the latest data, build a new DataFrame from the source tables.
Python
oct_15_orders = spark.table("main.sales.orders").filter(F.col("order_date") == "2026-10-15")
oct_15_orders.cache()
large_orders = oct_15_orders.filter(F.col("amount") > 1000)
orders_per_customer = oct_15_orders.groupBy("customer_id").count()
orders_per_customer.cache() # Has its own snapshot.
# Acknowledge that the cached data is no longer needed.
# This releases the storage and destroys oct_15_orders.
oct_15_orders.unpersist()
oct_15_orders.count() # Raises CACHE_DATAFRAME_ACCESS_AFTER_UNCACHE.
large_orders.count() # Raises CACHE_DATAFRAME_ACCESS_AFTER_UNCACHE.
orders_per_customer.count() # Reads its own snapshot.
# If you need the data again later, build a new DataFrame from the source tables.
oct_15_orders = spark.table("main.sales.orders").filter(F.col("order_date") == "2026-10-15")
Scala
var oct15Orders = spark.table("main.sales.orders").filter(col("order_date") === "2026-10-15")
oct15Orders.cache()
val largeOrders = oct15Orders.filter(col("amount") > 1000)
val ordersPerCustomer = oct15Orders.groupBy("customer_id").count()
ordersPerCustomer.cache() // Has its own snapshot.
// Acknowledge that the cached data is no longer needed.
// This releases the storage and destroys oct15Orders.
oct15Orders.unpersist()
oct15Orders.count() // Throws CACHE_DATAFRAME_ACCESS_AFTER_UNCACHE.
largeOrders.count() // Throws CACHE_DATAFRAME_ACCESS_AFTER_UNCACHE.
ordersPerCustomer.count() // Reads its own snapshot.
// If you need the data again later, build a new DataFrame from the source tables.
oct15Orders = spark.table("main.sales.orders").filter(col("order_date") === "2026-10-15")
df.storageLevel
Gets the storage level of df.
- Returns:
StorageLevel.DISK_ONLYwhiledfis cached.StorageLevel.NONEbefore you cache it and after you calldf.unpersist().
Python
from pyspark import StorageLevel
oct_15_orders.cache()
oct_15_orders.storageLevel == StorageLevel.DISK_ONLY # True
Scala
import org.apache.spark.storage.StorageLevel
oct15Orders.cache()
oct15Orders.storageLevel == StorageLevel.DISK_ONLY // true
For the method signatures, see the PySpark reference for cache, persist, unpersist, and storageLevel.
Lifecycle and time limits
Cached data exists only within the Azure Databricks session where you cache it. It becomes inaccessible when the session ends, and it lasts at most seven days. After the seven-day limit, using a cached DataFrame raises an error instead of reading the source tables again. To cache again, start a new session.
Storage and cleanup
Azure Databricks manages the storage for cached data automatically. Cached data is stored in cloud storage, with the specific storage location depending on your workspace type:
- Serverless workspaces: Cached data is stored in default storage. If you have configured a customer-managed key for managed services, cached data uses that key. See Customer-managed keys for encryption.
- Classic workspaces: Cached data is stored in the workspace storage account that is configured at workspace creation time. You can configure encryption at the storage account level.
After cached data becomes inaccessible, Azure Databricks automatically reclaims the storage in the background. This includes cases where the session ends because of a disconnection or a crash. Calling df.unpersist() allows Azure Databricks to reclaim the storage earlier.
Isolation and privileges
Any user who can run code on serverless compute can cache a DataFrame. You don't need any extra privileges.
Cached data provides session-level isolation. Only the session that cached a DataFrame can read its cached data. Other users can't access it, including other users of the same notebook.
The cached data contains what you were allowed to read when you cached it. Permission changes, row filters, and column masks applied to the source tables afterward don't apply to the cached data.
Best practices
- Cache when you need a stable snapshot: If you run several transformations, aggregations, or drill-downs on the same computed data, the snapshot gives them a consistent baseline that doesn't change when the source tables change.
- Cache expensive computations, not plain table reads:
df.cache()materializes the query result, and later reads scan that snapshot. If you cache a plain table read, such asspark.table("main.sales.orders").cache(), later reads still scan a full copy of the table, so caching saves little. Caching pays off after expensive work such as joins, aggregations, and complex filters. - Don't cache data that you read only one time:
df.cache()computes and stores the result right away, which takes time and storage. If you read the result only one time, the upfront cost isn't worth it. - Use the cache by deriving from the cached DataFrame: To read the snapshot, build later operations from the cached DataFrame variable, for example
df.select(...)ordf.filter(...). An independent DataFrame built from the same computation on the source tables doesn't use the cache. - Don't use cache for cross-session or cross-user data sharing: Cached data is private to your session. Other sessions, notebooks, job runs, and users can't read it. To share computed results across sessions or with other users, write them to a permanent table.
- Keep sessions with cached data under seven days: See Lifecycle and time limits. If your workflow needs the data for longer, write it to a table.
- Call
df.unpersist()when you no longer need the cached data: This allows Azure Databricks to reclaim the storage earlier. After the call, the DataFrame and the DataFrames derived from it can't be used. - Don't use
df.unpersist()to refresh data:df.unpersist()ends the snapshot's life. To pick up changes in the source tables, build a new DataFrame from the source tables.
Limitations
DataFrame caching on serverless compute has the following limitations:
- Column names and types: The following DataFrames can't be cached. Rename or cast the columns before you cache, for example with
.alias("total_amount").- DataFrames with duplicate column names.
- DataFrames with column names that contain spaces or any of the characters
,;{}()=, such assum(amount)from an aggregation. - DataFrames with interval columns.
- DataFrames with arrays, maps, or structs that contain NULL-typed values.
- DataFrames with no columns, or with only NULL-typed columns.
- Column references: After you cache a DataFrame, you can't reference its columns through the DataFrames it was built from. For example, after
oct_15_orders = orders.join(customers, "customer_id")andoct_15_orders.cache(),oct_15_orders.select(orders.amount)raisesCANNOT_RESOLVE_DATAFRAME_COLUMN. Useoct_15_orders.amountor"amount"instead. - SQL and catalog cache APIs: SQL cache statements, such as
CACHE TABLE, and the table cache methods ofspark.catalogaren't supported on serverless compute. See Caching limitations. To reuse a query result in SQL, use a session-local temporary table. - Information schema queries: When many sessions in a metastore cache DataFrames, queries on
information_schema.volumesandinformation_schema.schematacan be slow or time out. Filteringvolumesby catalog doesn't make the query faster. Queries oninformation_schema.tablesaren't affected.
- Azure Government regions: DataFrame caching isn't supported on serverless compute in Azure Government regions.
- Ignored parameters:
df.persist(storageLevel)ignores the storage level, anddf.unpersist(blocking)ignoresblocking. Seedf.persist(storageLevel)anddf.unpersist(blocking).
Migrate from classic compute
For most workloads, caching on serverless compute gives the same results as on classic compute, and existing code runs without changes. The following table lists the differences.
| Behavior | Serverless compute | Classic compute |
|---|---|---|
| Cache matching | Only the cached DataFrame and the DataFrames derived from it automatically use the cache. | Any query with the same plan shape automatically uses the cache. |
| Data freshness | A consistent snapshot taken at the time you call df.cache(). |
Varies: cached data is refreshed in certain cases and serves a snapshot in others. |
| Sharing scope | Private to your session. | Shared across sessions within the cluster. |
| Materialization | Eager: computed right away when you call df.cache(). |
Lazy: computed at the first action on the DataFrame. |
df.unpersist() |
Destroys the DataFrame and releases its snapshot deterministically. | Releases the in-memory cache. Later use of the DataFrame may get the latest or stale data, depending on the product and data source. |
The following describes the first two differences in more detail:
- Cache matching:
- Workloads affected by the change: Most workloads aren't affected. If you keep using the cached DataFrame variable, for example with
df.select(...)ordf.filter(...), the cache works as before. The change affects only workloads where an independently built DataFrame or SQL query reuses the cache because it has the same plan shape. On serverless compute, those queries read the source tables. - If your workload is affected: Use the cached DataFrame variable wherever you need the cached result, instead of building the same query again.
- Workloads affected by the change: Most workloads aren't affected. If you keep using the cached DataFrame variable, for example with
- Data freshness:
- Workloads affected by the change: Most workloads aren't affected, because reading cached data again after writing to its source tables is uncommon. The change affects only workloads that write to the source tables and then expect the cached DataFrame to show the new data.
- If your workload is affected: After you write to the source tables, build a new DataFrame from them instead of reusing the cached one.