Monitor and Optimize an Analytics Solution · Topic 4 of 5
Optimizing Lakehouse Tables and Spark Performance
DP-700 optimization: OPTIMIZE, V-Order, Z-Order and VACUUM for Delta tables, partitioning, the native execution engine, pools and efficient Spark code.
- 5 min
- OPTIMIZE compacts files; V-Order speeds reads; Z-Order clusters by filter columns.
- VACUUM deletes unreferenced old files; default retention is 7 days.
- Partition only large tables on low-cardinality columns.
What it is
A lakehouse table is a folder of Parquet files plus a Delta log. It performs well when it has a moderate number of large, well-organized files. Frequent small writes, streaming and merges create many small files, and every query then pays to open them.
Spark performance depends on the same files and on the compute: pool and node sizes, session start-up, the engine used, and code that keeps work distributed.
How it works
Optimize a lakehouse table
| Technique | What it does | When to use |
|---|---|---|
OPTIMIZE | Compacts small files into larger ones | Tables written by frequent small batches, streaming or merges |
| V-Order | Write-time Parquet optimization (sorting, encoding, compression) that speeds up reads by Direct Lake, the SQL analytics endpoint and Spark | Tables read often, especially by Power BI |
ZORDER BY | Co-locates rows with similar values so queries skip files | High-cardinality columns used in filters, such as customer_id |
VACUUM | Deletes files no longer referenced by the Delta log and older than the retention period | Regularly, to cut storage; it limits time travel |
| Partitioning | Splits the table into folders by a column | Large tables filtered by a low-cardinality column such as year |
| Optimize write | Spark setting that produces fewer, larger files at write time | Write paths that would otherwise create many small files |
%%sql
OPTIMIZE sales_orders ZORDER BY (customer_id) VORDER;
VACUUM sales_orders RETAIN 168 HOURS;
The default retention for VACUUM is 7 days (168 hours). A shorter period removes the files that time travel and long-running readers need, so Delta blocks it unless you deliberately disable the safety check.
You can also run these from the lakehouse explorer: choose Maintenance on a table to run OPTIMIZE (optionally with V-Order) and VACUUM with a retention threshold. A notebook or pipeline can schedule maintenance for many tables.
Partition only large tables, on columns with few distinct values. Partitioning by a high-cardinality column such as order_id or a timestamp creates thousands of tiny files and makes queries slower.
Optimize Spark performance
Compute choices
| Setting | Effect |
|---|---|
| Starter pool | Sessions start in seconds; good for interactive and short jobs |
| Custom pool | Right-size nodes and autoscale for heavy jobs |
| Dynamic allocation | Adds and removes executors as the job needs them |
| High concurrency | Notebooks share a session, so they skip start-up time |
| Native execution engine | Runs Spark SQL and DataFrame operations in a vectorized C++ engine; no code changes; falls back to standard Spark for unsupported operations |
Enable the native execution engine in an environment's acceleration settings, or for one session:
%%configure
{
"conf": {
"spark.native.enabled": "true"
}
}
Code choices
- Filter and select columns early, so Delta can skip files and Parquet reads fewer columns.
- Broadcast small dimension tables in joins, so the large table is not shuffled.
- Prefer built-in functions to Python UDFs, which move data between the JVM and Python.
cache()a DataFrame only when it is reused several times, and unpersist it after.- Avoid
collect()andtoPandas()on large data; write results to a table. - Repartition on a better key when one task runs far longer than the rest (data skew).
from pyspark.sql import functions as F
fact = spark.read.table("fact_sales").where(F.col("order_date") >= "2026-01-01")
dim = spark.read.table("dim_product").select("product_key", "category")
result = (fact.join(F.broadcast(dim), "product_key")
.groupBy("category")
.agg(F.sum("amount").alias("revenue")))
Adaptive query execution is on by default in Spark 3, so Spark already coalesces shuffle partitions and can switch join strategies at run time. Use the Spark UI to see slow stages, spills to disk and skewed tasks.
A worked example
A streaming job appends to iot_readings every minute. After three months, queries that took seconds take minutes, and the table folder holds hundreds of thousands of small files.
A nightly notebook runs OPTIMIZE iot_readings ZORDER BY (device_id) VORDER and a weekly VACUUM with the default retention. Queries filtering by device now skip most files. The team also turns on the native execution engine in the environment for the aggregation notebooks.
How the exam asks about this
- "A Delta table has many small files and slow queries." Run
OPTIMIZE. - "Improve Direct Lake and SQL endpoint read performance of a lakehouse table." Apply V-Order.
- "Remove unreferenced files to reduce storage while keeping 7 days of time travel."
VACUUMwith the default 168 hour retention.
What to remember for the exam
- OPTIMIZE compacts files; V-Order speeds reads; Z-Order clusters by filter columns.
- VACUUM deletes unreferenced old files; default retention is 7 days.
- Partition only large tables on low-cardinality columns.
- The native execution engine accelerates Spark with no code changes.
- Broadcast small tables, avoid Python UDFs and
collect(), and check the Spark UI for skew.
Running VACUUM with a very short retention breaks time travel and can fail queries or streams still reading older files. Keep the default unless you have a strong reason.
Common mistakes
Partitioning by date and hour on a small table. It multiplies small files; a small table needs no partitions.
Expecting V-Order to fix a small file problem. V-Order organizes data within files; OPTIMIZE reduces the number of files.
Scaling up nodes before fixing code. A collect(), a Python UDF or a skewed join usually costs more than node size.
Check yourself
Which command compacts many small files in a Delta table?
OPTIMIZE, optionally withZORDER BYandVORDER.What is the default retention period for VACUUM?
7 days (168 hours); shorter values are blocked by a safety check unless it is disabled.
How do you speed up a join between a large fact table and a small dimension table in Spark?
Broadcast the small table, for example with
F.broadcast(dim), so the large table is not shuffled.
Topic 4 of 5 in Monitor and Optimize an Analytics Solution
Test yourself on Monitor and Optimize an Analytics Solution
The full Microsoft DP-700 question bank has 645 questions, with timed practice exams and an explanation for every answer.
This study guide is independent and is not affiliated with, endorsed by, or sponsored by Microsoft. Vendor names, certification names and exam codes are trademarks of their respective owners and are used for identification only.