Skip to content

Iceberg performance follow-ups

This cohort starts from merged main 0a02ecb8. Release + mimalloc libraries are unchanged; their hashes are in environment.json. Data-file payloads remain Arrow and Rust Parquet. Java retains routing, delete encoding, rolling, checkpoints and commits. Acceleration remains enabled by default. Streaming only.

State installation component

state-installation.jsonl retains every warmup and measured iteration from two independent previous/current JVM pairs. The real production canonical-state writer installs synthetic native partitions in the real Flink heap backend. Both sides use 4 GiB heaps, two warmups and five measurements per size. Setup, restore/readback, native encoding and filesystem writes are excluded. Thread allocation requests are measured with the JDK ThreadMXBean; they are not copied-byte measurements. Compressed process logs retain all assertions.

Payload / groups Previous medians, ms Current medians, ms Previous / current allocation, MiB
4 KiB / 128 0.652 / 0.777 0.593 / 0.518 1.098 / 0.596
2 MiB / 128 76.964 / 74.670 48.877 / 50.975 512.098 / 256.096
4 MiB + 1 / 16 23.060 / 26.072 18.316 / 18.225 128.018 / 64.017

This validates the removal of a full temporary payload array. It does not establish an end-to-end checkpoint speedup. The saved format and actual checkpoint bytes do not shrink.

Fresh state-heavy baseline

state-before/results.md retains the unchanged q7/q18/q20 memory/off comparisons: 2M events, four input/output buckets, p4, equal 4 GiB heaps, one warmup and three alternating measured pairs, standard snapshot compression disabled. All 12 pairs pass committed-result parity and native source/writer/footer witnesses. All timings retain configured checkpoints and final drain/commit. The fresh baseline ratios are 0.33× / 1.21× / 0.33× stock, with large ranges on both engines.

The seven-second kernel probe retains 134 blocked-thread observations: 133 on async checkpoint workers, including 112 folio_wait_bit_common and 21 jbd2_log_wait_commit waits; one is an Iceberg worker cloning a thread. Concurrent thread observations cannot be summed into wall time. Stock q18 ranges 4.024–43.104 s and native 4.222–32.976 s. This reproduces storage variability on both engines and does not establish a Parquet read/write bottleneck. Temporary-copy removal reduces allocation and CPU work while leaving the physical checkpoint format and standard compression setting unchanged.

Dynamic allocation diagnostic

dynamic-profile-before/ retains a separate whole-JVM CPU/allocation diagnostic with one warmup and one measured pair for append and keyed format-v2 writing. It includes setup, stock trials and untimed readback. Profiled durations are excluded from primary claims. Inclusive sampled allocations under metadata extraction total 2.76 GB and control-record construction 0.89 GB; these categories overlap other inclusive adapter counts. Extraction includes reflection argument arrays and repeated equality-set copies. CPU sample counts are overlapping observations, not elapsed-time percentages.

The experimental direct-main and incomplete-classpath probes were stopped and excluded: they did not reproduce the normal shared-cluster lifecycle. Complete comparisons use JUnit discovery and the repository's normal shared-cluster extension, 48 GiB task-budget bookkeeping, 4 GiB heaps and streamfusion.exchange.zeroCopyLocal=false. These bookkeeping budgets do not allocate 48 GiB of physical memory. Only matched complete cohorts are used for performance claims.

Metadata-only intermediate probe

The complete intermediate metadata-probe/ cohorts retain 64 validated v2/v3 writer trials (one warmup and three measured pairs per mode/format). They use typed handles and the initial metadata cache, before integer-partition reuse and identity hashing. For the complete previous/native comparison, source and release library baselines are frozen from 0a02ecb8.

Format / mode Previous stock / native median, s Intermediate stock / native median, s
v2 append 2.349 / 1.817 2.378 / 1.877
v2 keyed 2.983 / 2.752 3.591 / 3.911
v3 append 2.464 / 1.870 2.332 / 1.734
v3 keyed 2.784 / 2.131 3.002 / 2.117

Metadata reuse alone does not establish a consistent end-to-end gain; the unfavorable v2 keyed observations remain. The final selector optimization requires new complete comparisons. All committed payloads, native footers, v3 keyed Puffin deletes and resource release assertions pass. Per-trial observations retain ranges, file counts and resources.

file-task-before/ retains the fresh 262,144-row main baseline for reads, writes and normal/dynamic generator routing. It uses the existing fast-loop method, two warmups and five alternating repetitions. File read/write medians are 1.94× / 1.41× stock. This is a component baseline, separate from streaming lifecycle costs.

Bounded partition-key reuse

The first broad integer-tuple prototype wins with four buckets but loses with 16,384 buckets: the cache clears frequently and misses repeat partition transforms. Both complete high-cardinality cohorts remain in dynamic-key-cardinality* (0.60× / 0.62× stock). The final optimization caches only bucket combinations whose maximum tuple cardinality, including nulls, fits 1,024 entries. Identity/time and larger bucket spaces retain the original dynamic selector. The data path continues to accelerate by default.

Case Stock median [range], ms Arrow median [range], ms Ratio
Four buckets, final 30.19 [28.83–31.26] 12.55 [11.76–13.16] 2.41×
16,384 buckets, bounded admission 29.45 [27.84–33.23] 29.49 [28.59–38.08] 1.00×

Two earlier four-bucket repeats give 2.16× / 2.30×. The fixture takes about two seconds per process, verifies every original Java-selected writer key, and excludes the full catalog/processor, shuffle, writing and lifecycle. component-final/ verifies reads, writes and existing route/generator paths; their native encoders are unchanged and cohort drift must not be attributed to the new dynamic routing changes.

After-allocation diagnostic

dynamic-profile-after/ retains another separate whole-JVM diagnostic with the same one-warmup/one-measured-pair boundary as before. It overlaps unrelated local validation work and is excluded from unprofiled performance claims. Sampled allocation estimates under metadata extraction fall 2.761→0.315 GB (8.78× reduction); control-record construction falls 0.893→0.372 GB. Inclusive dynamic-adapter allocations fall 12.815→8.495 GB. Categories overlap and include setup/readback reached from those frames; they cannot be summed or treated as copied bytes. dynamic-inclusive-counts.csv records numerators and all-JVM denominators. Runtime samples in the partition memoizer confirm that the decorated released generator actually uses it. The reduction preserves borrowed Arrow views and original Java controls.

Correctness checks

The complete released streaming Iceberg selection passes 309 tests, with 13 explicit batch exclusions. This includes unchanged upstream SQL/control tests, branch/upsert writers, native reads/writes, source recovery and real captured Arrow-channel recovery with rescaling. Ten focused shared-state tests also pass: chunk boundary ownership and restore, CRC/version checks, incremental RocksDB checkpoints and canonical backend migration. The shared-state fixture compiles against the runtime's Java 11 API target. Compressed logs and exact Maven commands are retained in validation/.

The final bounded-bucket guard and factored handle signature additionally pass 18 focused dynamic/partition tests, including all eight live dynamic-sink cases. This adds compound-bucket/null cardinality and overflow checks to the original cache, metadata-mutation, branch/upsert and recovery selection.

Complete dynamic writer cohorts

All complete cohorts below use 2M Arrow rows, p4, four buckets per target, Zstd 3, 4 GiB heaps, the normal shared-cluster lifecycle, Arrow IPC shuffle, one warmup and three alternating measured pairs per mode. Every trial validates all committed payloads, native data footers, v3 keyed Java Puffin deletes and resource release. Timing includes routing, shuffling, encoding, checkpoints and final commit.

Cohort Format / mode Stock median [range], s Native median [range], s Stock/native
baseline-dynamic-v2 v2 / forward-hash 2.688 [2.588–2.709] 1.783 [1.735–2.011] 1.51×
baseline-dynamic-v2 v2 / keyed 3.980 [3.813–4.933] 3.423 [3.016–3.607] 1.16×
baseline-dynamic-v3 v3 / forward-hash 2.554 [2.398–2.924] 2.145 [1.849–2.148] 1.19×
baseline-dynamic-v3 v3 / keyed 2.668 [2.613–3.596] 2.202 [2.170–2.498] 1.21×
bounded-current-v2 v2 / forward-hash 2.394 [2.335–2.477] 1.662 [1.607–1.803] 1.44×
bounded-current-v2 v2 / keyed 3.587 [3.423–3.736] 2.766 [2.500–3.034] 1.30×
bounded-current-v3 v3 / forward-hash 3.389 [3.018–3.441] 1.780 [1.664–3.086] 1.90×
bounded-current-v3 v3 / keyed 3.508 [2.935–3.832] 3.225 [2.940–9.048] 1.09×
idle-previous-v2 v2 / forward-hash 2.348 [2.285–2.527] 1.769 [1.719–1.872] 1.33×
idle-previous-v2 v2 / keyed 3.573 [3.330–3.774] 2.704 [2.495–2.915] 1.32×
idle-current-v2 v2 / forward-hash 2.687 [2.656–3.404] 2.001 [1.651–2.607] 1.34×
idle-current-v2 v2 / keyed 3.725 [3.100–3.737] 3.301 [2.341–4.151] 1.13×
idle-current-v3 v3 / forward-hash 2.390 [2.341–2.778] 1.603 [1.559–2.597] 1.49×
idle-current-v3 v3 / keyed 3.853 [3.258–4.365] 2.346 [2.180–2.382] 1.64×
idle-previous-v3 v3 / forward-hash 2.385 [2.325–2.410] 2.050 [1.807–2.544] 1.16×
idle-previous-v3 v3 / keyed 2.617 [2.587–2.837] 2.092 [2.058–2.189] 1.25×

The idle-* cohorts were launched after the machine became idle; another session started short Java workloads during collection. Their host-load log is retained. The earlier bounded-current-v3 cohort overlaps a larger foreign test run and keeps its 9.048 s native keyed observation. No samples are dropped or replaced.

These complete-job cohorts do not establish a consistent gain over previous native behavior: append gains and keyed regressions coexist with changing stock baselines and checkpoint/file-count variation. The measured benefit is lower routing/allocation cost at the component boundary; whole-job limits remain explicit. The full normal-SQL Nexmark matrix is evaluated separately.