Lazy and eager scan metadata
Use the provider-execution benchmark to compare repeated queries against one loaded table. Run a matched pair in which only the scan metadata mode and output path differ:
cargo bench --locked -p delta-arrow-reader --bench reader --all-features -- \
--mode provider-exec \
--seed 0 \
--output target/eager-metadata-lazy.csv \
--provider-exec-temp-dir target/eager-metadata-bench \
--provider-exec-storage-profile local \
--provider-exec-workload provider_many_unequal_files \
--provider-exec-query filter_tail_ids \
--provider-exec-backend direct-parquet \
--provider-exec-scan-metadata-mode lazy \
--provider-exec-queries-per-table 3 \
--provider-exec-scheduling-profile prefetch_2_ap_target_scan_3x \
--provider-exec-parquet-metadata-size-hint-bytes 65536 \
--provider-exec-parquet-full-file-read-threshold-bytes disabled \
--provider-exec-repetitions 5
cargo bench --locked -p delta-arrow-reader --bench reader --all-features -- \
--mode provider-exec \
--seed 0 \
--output target/eager-metadata-eager.csv \
--provider-exec-temp-dir target/eager-metadata-bench \
--provider-exec-storage-profile local \
--provider-exec-workload provider_many_unequal_files \
--provider-exec-query filter_tail_ids \
--provider-exec-backend direct-parquet \
--provider-exec-scan-metadata-mode eager \
--provider-exec-queries-per-table 3 \
--provider-exec-scheduling-profile prefetch_2_ap_target_scan_3x \
--provider-exec-parquet-metadata-size-hint-bytes 65536 \
--provider-exec-parquet-full-file-read-threshold-bytes disabled \
--provider-exec-repetitions 5
Each repetition loads and registers a new table, then runs the configured query
three times against that same table. Keep --seed, the temporary directory,
workload, query, backend, storage, scheduling, Parquet settings, repetition
count, and query count identical between the two commands. Use at least three
queries per table so the comparison includes metadata reuse rather than only
the initialization tradeoff.
The CSV separates the relevant timing boundaries:
table_initialization_micros_*measures table loading. It ends before DataFusion table registration.planning_micros_*measures SQL and physical-plan creation for each query, including Delta scan planning. Its percentiles include every query from every repetition.total_micros_*measures planning and execution for each query, but excludes table initialization and registration.session_total_micros_*starts immediately before table loading and ends after all benchmark queries, output checks, and metrics collection finish. It includes initialization, registration, planning, and execution. Its percentiles use one complete session per repetition.
Before comparing timings, confirm that both CSV rows have the same
fixture_fingerprint, seed, workload, query, storage, backend, scheduling,
repetition count, and query count. scan_metadata_mode should be the only
lifecycle setting that differs.
Eager mode moves Delta log/checkpoint replay into table initialization, so first
compare table_initialization_micros_* and planning_micros_* to confirm that
the work moved between phases. Then compare session_total_micros_* to judge
the end-to-end effect for the chosen number of queries. The per-query
total_micros_* fields help show whether execution time masks a planning-time
difference.
Do not treat one query count as a universal break-even point. The result depends on Delta history and checkpoint shape, active-file count, object-store latency, query selectivity, available statistics, memory pressure, hardware load, and the number of queries that reuse the loaded table. The local fixture above is a reproducible comparison of the two lifecycles; measure representative tables on their real storage before choosing a production default.
Representative real-S3 result
On August 27, 2026, we also compared the modes against the latest snapshots of
two production-shaped tables in S3. This used the direct Parquet backend through
DataFusion, the unreleased eager-metadata implementation at commit a6adb83,
16 target partitions, an 8,192-row batch size, and the same machine as the
reader comparison. One warmup pair was
discarded. Four measured pairs alternated whether lazy or eager mode ran first;
each isolated process loaded both tables once, then ran the HIP and schedule
queries three times each.
Table initialization and complete-session values below are medians across the
four processes for each mode. Planning values are medians across the 12 query
executions for each table and mode.
| Measurement, median | Lazy | Eager | Difference |
|---|---|---|---|
| Table initialization | 1.196 s | 4.089 s | +2.894 s |
| Complete six-query session | 50.205 s | 42.451 s | -7.754 s (-15.4%) |
| HIP physical planning | 2.097 s | 5.1 ms | -99.76% |
| Schedule physical planning | 756 ms | 1.1 ms | -99.86% |
Both modes returned the same results and performed the same Parquet I/O. Each HIP query returned 15,252,906 rows in 2,483 batches and read 505,642,366 bytes with 1,044 full-file GETs. Each schedule query returned 58,161 rows in 12 batches and read 1,217,686 bytes with four range GETs and four full-file GETs.
We measured memory separately because allocator state from a completed query can
hide the retained-cache cost. Across six counterbalanced initialization-only
pairs, loading both tables increased median resident memory from 38.5 MiB to
68.7 MiB, a 30.2 MiB increase. Across four counterbalanced pairs that each ran
one HIP and one schedule query, median peak resident memory increased from
233.9 MiB to 248.9 MiB, a 15.0 MiB or 6.4% increase. These Linux measurements
used VmRSS and VmHWM from /proc/self/status.
This is a dated case study, not a universal performance promise or a pinned public fixture. The live table snapshots can advance. Cache memory scales with active-file metadata, statistics width, partition values, and deletion-vector metadata rather than table row count or total Parquet data size.