Compaction, OPTIMIZE and Z-order
Rewrite small files into large ones and cluster data so queries skip more of it.
On this page
Show code in
Every code block on the page follows this.
You will learn
- What compaction does and how to run it on Delta and Iceberg
- Why data clustering matters for file skipping
- How Z-ordering clusters by several columns at once
- How often to run maintenance, and what it costs
Read first
- Parquet and columnar storage · 4 min read
- The small file problem · 6 min read
Comfortable with these? Read on.
OPTIMIZE (Delta) and rewrite_data_files (Iceberg) rewrite many small files into fewer large ones in one atomic commit. Adding ZORDER BY (a, b) or a sort order also clusters rows so each file covers a narrow range of those columns, letting queries skip more files.Compaction
from delta.tables import DeltaTable DeltaTable.forName(spark, "events").optimize() \ .where("event_date >= '2025-03-01'").executeCompaction()
-- Delta Lake OPTIMIZE events WHERE event_date >= '2025-03-01'; -- Apache Iceberg CALL catalog.system.rewrite_data_files( table => 'db.events', where => 'event_date >= "2025-03-01"');
- Delta OPTIMIZE bin-packs files toward a target size (1 GB by default, configurable).
- Iceberg rewrite_data_files targets
write.target-file-size-bytes(512 MB by default) and supports bin-pack and sort strategies. - Both commit atomically: readers see either the old files or the new ones. Old files stay until VACUUM or snapshot expiration.
- Compaction is idempotent: running it on already compacted data does little.
Why clustering matters
File skipping uses min/max statistics per file. If rows are in arbitrary order, every file contains every customer, every file's range for customer_id spans almost everything, and a filter on one customer reads all files. Clustering puts similar values together so ranges are narrow.
| Layout | Files read for customer_id = 42 |
|---|---|
| Random order, 1,000 files | ≈ 1,000 (each file spans all ids) |
| Sorted by customer_id | 1 or 2 |
| Z-ordered by (customer_id, product_id) | A few, and also a few for a product_id filter |
Z-ordering
Sorting by one column clusters only that column perfectly; the second sort column is clustered only within ties of the first. A Z-order curve interleaves the bits of several columns, so rows close in any of them tend to land in the same file. It gives good, not perfect, skipping on each column.
-- Delta Lake OPTIMIZE events ZORDER BY (customer_id, product_id); -- Apache Iceberg CALL catalog.system.rewrite_data_files( table => 'db.events', strategy => 'sort', sort_order => 'zorder(customer_id, product_id)');
- Choose columns used in filters and joins with many distinct values, such as ids. Low-cardinality columns are better as partitions or not needed.
- Effectiveness drops with each added column; two to four is typical.
- Z-ordering a column with no statistics (beyond the first 32 columns in Delta) is useless.
- Delta Z-order is not incremental: re-running it rewrites the affected data. Liquid clustering is the incremental successor.
How often
Compaction costs compute: it reads and rewrites data. Common practice is a daily or weekly job per table, limited to recent partitions with a WHERE clause, since old partitions rarely change. Streaming tables with many small commits need it more often, or auto compaction. Measure the average file size (DESCRIBE DETAIL, or Iceberg's files table) and the bytes scanned by common queries before and after to decide.
Common mistakes
Z-ordering by a low-cardinality column
Z-ordering by many columns
Compacting the whole table every day
Key takeaways
- Compaction rewrites small files into large ones in one atomic commit.
- File skipping only works when data is clustered by the filtered column.
- Z-order clusters by several columns, with decreasing effect per column.
- Schedule maintenance on recent data and measure the gains.
Check yourself
3 questions1. Why does Z-ordering speed up filters?
Show the answer
It clusters similar values into the same files, narrowing per-file min/max ranges. Narrow ranges let file statistics rule out most files.
2. Which column is the best Z-order candidate?
Show the answer
customer_id (millions of values, often filtered). High-cardinality, frequently filtered columns benefit most.
3. What happens to readers while OPTIMIZE runs?
Show the answer
They keep reading the old version until the new one commits. Compaction is one atomic commit; readers use snapshots.
Practice it
Interview problems that use this: write the PySpark, run it, and get graded on hidden tests.
Go deeper
Primary sources: Delta: optimizations · Iceberg: maintenance