Skip to content
Good engineers know 3 min read · Delta, Iceberg 1 practice problem ↓

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

Comfortable with these? Read on.

TL;DR 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

PySparkSpark SQL
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.

LayoutFiles read for customer_id = 42
Random order, 1,000 files≈ 1,000 (each file spans all ids)
Sorted by customer_id1 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.

Spark SQL
-- 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

Gains little; partitioning or nothing is better.

Z-ordering by many columns

Each column gets weaker clustering.

Compacting the whole table every day

Rewrites unchanged data. Limit to recent partitions.

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 questions

1. 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.

Solve: Find Partitions That Need Compaction →

Go deeper

Primary sources: Delta: optimizations · Iceberg: maintenance