Lake Optimization
The file is your query plan
Lakes get optimized the way files do: sort/write aligned with predicates, viable file sizes, compaction to kill tiny files, stats honesty, and column projection. Small files are the universal first evil.
What you will learn
- Diagnose the small-files disease and cure with compaction
- Align sorting with predicate columns for pruning
- Right-size files and row groups for parallelism
What is this?
Lake query speed is a file-layout problem: few big, sorted, stats-honest Parquet files beat millions of tiny unsorted ones. Optimization = right-size files, sort by predicate columns, compact after bursts, keep statistics sane, and project columns — usually re-runnable 'bin-packing' jobs over raw output.
Why do you need it?
- A well-optimized lake can match a warehouse; a neglected one pays 100x scan cost.
- Compaction and sorting are cheap, idempotent operations that pipelines can run daily.
- Metadata/stats honesty directly translates to 'scans fewer bytes per query'.
Mental model
Shoeboxes vs. a closet: all socks in one issue vs one sock per drawer
File sizing
Thousands of one-sock drawers (tiny files) means opening hundreds of drawers per lookup; a few sorted shoeboxes (compacted files) lets you pull the right drawer and grab the exact color (sorted range) without opening everything.
How it works
- Targets: 128MB-1GB per Parquet file, row groups aligned to scan entry points.
- Sort/rewrite by the dominant predicate so lower/upper bound stats become true range filters.
- Compaction merges small files into larger ones and can re-sort (OPTIMIZE / Z-order / rewrite).
- Statistics honesty: sorted and stats-bearing files let engines skip whole files/row groups.
- Projection discipline: narrow schemas and explicit column selection cut every scan.
The small-files-to-compacted pipeline.
Key commands
duckdb -c "SELECT count(*), round(avg(size)/1024/1024,1) mb FROM parquet_metadata('lake/**/*.parquet');"Count files and average size — the small-files diagnostic.
duckdb -c "COPY (SELECT * FROM read_parquet('lake/**/*.parquet') ORDER BY ts) TO 'lake/opt/out.parquet' (FORMAT PARQUET, ROW_GROUP_SIZE 122880);"One-pass sort+coalesce into bigger, prunable files.
Examples
Sort + compact in one rewrite
-- Every night: rewrite by predicate column and coalesce filesOPTIMIZE lake.orders ZORDER BY (ts); -- delta/iceberg style, sorts + compacts -- DuckDB analog, same idea:COPY ( SELECT * FROM read_parquet('lake/raw/orders/*.parquet') ORDER BY ts, customer_id) TO 'lake/curated/orders/orders.parquet' (FORMAT PARQUET, ROW_GROUP_SIZE 122880);Compaction AND sorting are the same job when you rewrite a partition; the sorted output's stats finally work for the filter.
In production
- A nightly streaming-dump compaction merges 40k tiny files into 120 big sorted ones before BI opens.
- A monthly 'rewrite orders by ts' keeps the quarter-end report scans under budget.
- A stats campaign re-writes unsorted tables once, then predicate pushdown starts working.
Best practices
- Tie compaction cadence to write volume: the busier the writes, the more often you compact.
- Sort by the column that appears in most queries (usually a timestamp), then a cardinal discriminator.
- Keep row groups ~128MB-equivalent; more parallelism splits files, not pages.
- Budget storage for the duplicate copies during rewrite and set a retention to free them.
Common mistakes
- Letting 100k small files accumulate 'because we delete later' — the delete itself becomes the slow twin.
- Sorting by a column nobody filters on, buying nothing.
- Compacting without bounding the process, doubling one partition's storage indefinitely.
Debugging tips
- Slow scan = inspect file counts/sizes first; tiny files explain themselves.
- A filter that still reads everything = unsorted data (or a wrapped predicate); re-sort by the filter column.
- Compare bytes read per query profile before/after compaction to prove the win.
Interview checkpoint
Hands-on lab
- Generate thousands of tiny parquet files to simulate streaming dumps
- Measure file count and average size with parquet_metadata
- Compact + sort into a few big files and confirm the difference
Diagnose and Fix the Small-Files Epidemic
30 min · intermediate
Quick quiz
The first optimization most lakes need is…
Sorting data by the column you filter on makes Parquet row-group statistics actually exclude irrelevant groups.