Repository navigation
Weekly TPC-H SF1000 with memory monitoring; fix DataFusion Vortex scan over-decoding - #10567
joseph-isaacs wants to merge 5 commits into
Conversation
Adds a weekly (and manually dispatchable) TPC-H SF=1000 run on NVMe for DataFusion and DuckDB over Parquet and Vortex, one machine per engine. Parquet is generated with tpchgen-cli 3.0.0 (parallel, ZSTD(3)) because the in-repo generator writes each table single-threaded, then data-gen converts to Vortex. A stdlib-only scripts/bench-memory.py samples system memory and the largest process throughout, runs each query in its own process (OOM score raised so the runner survives) and records per-query status, wall time and peak RSS, rendered into the job summary. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FDifRZhxcVd5gvt3dsDhu5
The bench binaries refuse the default runner ID in CI. Label records with the EC2 instance runs-on provisioned, as vx-bench does. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FDifRZhxcVd5gvt3dsDhu5
DataFusion Q18 at SF1000 took the whole runner down (the agent got a shutdown signal) instead of only the query, losing the job's summary and artifacts. Poll available memory while each query runs and kill it below a threshold, and stop systemd-oomd, which kills whole cgroups regardless of the query's raised OOM score. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FDifRZhxcVd5gvt3dsDhu5
DataFusion does not yet fit Q18 (Parquet and Vortex) or Q21 (Parquet) in memory at SF1000. Record OOM kills, timeouts and errors in the summary and keep the weekly job green so it tracks both engines. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FDifRZhxcVd5gvt3dsDhu5
vortex-datafusion: a Vortex scan runs `concurrency * available_parallelism` splits per stream, and DataFusion opens one stream per partition, one per core by default, so Vortex's default concurrency of 4 multiplied with it. TPC-H SF1000 on 96 cores kept ~37k decoded splits in flight: DataFusion Q1 on Vortex peaked at 115 GiB against Parquet's 37 GiB. Default the scan concurrency to 1 under DataFusion when the table options leave it unset; `vortex.scan_concurrency` still overrides it. At SF1000 this cut Q1 to 32 GiB and Q5 from 146 to 52 GiB, sped Q5 and Q8 from ~18s to ~12s, and let Vortex Q9 complete. datafusion-bench: opt-in DATAFUSION_BENCH_MEMORY_LIMIT_GIB (FairSpillPool behind TrackConsumersPool), DATAFUSION_BENCH_SPILL_DIR and DATAFUSION_BENCH_MAX_SPILL_GIB. Without a pool DataFusion never spills. The weekly workflow runs DataFusion a second time with sort-merge joins (which spill, unlike hash join builds), an 80 GiB memory limit and a 500 GiB spill cap, labelled with run-queries' new --variant. bench-memory.py also reports DataFusion's resources-exhausted error as its own status. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FDifRZhxcVd5gvt3dsDhu5
Merging this PR will regress 3 benchmarks
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ ℹ️ | Simulation | test_scan_filter_to_arrow_array[int_float] |
3.5 ms | 4.1 ms | -13.63% |
| ❌ ℹ️ | Simulation | test_repeated_scan_filter[int_float] |
3.4 ms | 3.9 ms | -10.95% |
| ❌ ℹ️ | Simulation | test_repeated_scan_filter[int] |
2.5 ms | 2.7 ms | -10.24% |
| ⚡ | WallTime | threshold_neon[8] |
2.4 µs | 1.3 µs | +79.33% |
| ⚡ | WallTime | filter_neon[8] |
1,339 ns | 921 ns | +45.39% |
| ⚡ | WallTime | threshold_neon[80] |
4.9 µs | 3.4 µs | +44.31% |
| ⚡ | WallTime | threshold_neon[64] |
3.5 µs | 2.5 µs | +38.34% |
| ⚡ | WallTime | filter_neon[64] |
2.3 µs | 1.8 µs | +30.44% |
| ⚡ | Simulation | isolated_runs[u64, (161, 944)] |
168.2 µs | 137.8 µs | +22.06% |
| ⚡ | Simulation | embedding_code_runs_expanded_baseline[4096] |
1.9 ms | 1.6 ms | +20.63% |
| ⚡ | Simulation | embedding_code_runs_expanded_baseline[2048] |
1,016.3 µs | 850.6 µs | +19.48% |
| ⚡ | Simulation | embedding_code_runs_expanded_baseline[1024] |
548.4 µs | 465.2 µs | +17.89% |
| ⚡ | Simulation | isolated_runs[u64, (256, 896)] |
219.7 µs | 189.3 µs | +16.07% |
| ⚡ | Simulation | embedding_runs_expanded_baseline[1024] |
609 µs | 525.7 µs | +15.85% |
| ⚡ | Simulation | embedding_runs_expanded_baseline[1536] |
913.6 µs | 789.1 µs | +15.77% |
| ⚡ | Simulation | fragmented_runs_expanded_baseline[1024] |
623.2 µs | 540.2 µs | +15.36% |
| ⚡ | Simulation | embedding_code_runs_expanded_baseline[512] |
319.9 µs | 278.9 µs | +14.71% |
| ⚡ | Simulation | embedding_runs_expanded_baseline[768] |
516.1 µs | 454.7 µs | +13.51% |
| ⚡ | WallTime | bitpack_blocked_compress_avx2 |
7.6 µs | 6.7 µs | +13.26% |
| ⚡ | Simulation | fragmented_runs_expanded_baseline[512] |
369.9 µs | 329 µs | +12.44% |
| ... | ... | ... | ... | ... | ... |
ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing ji/tpch-sf1000-weekly (5aae915) with develop (6289e58)2
Footnotes
-
12 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
-
No successful run was found on
develop(bc9e0e6) during the generation of this report, so 6289e58 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩
Summary
Adds a weekly TPC-H SF=1000 run on NVMe for DuckDB and DataFusion over Parquet and Vortex. At this scale the data (~6B lineitem rows) is larger than the runner's memory, so the run is as much a memory check as a benchmark: it records per-query status, wall time and peak memory, and samples system memory throughout data generation, conversion and every query.
The branch runs found that DataFusion on Vortex decoded far more data at once than on Parquet; this PR fixes that in
vortex-datafusion. Results onc8gd.metal-24xl(188 GiB):DataFusion's hash join keeps its whole build side in memory and cannot spill, so the weekly run also runs DataFusion a second time with sort-merge joins and spilling. That configuration is not validated yet: its first attempt hit DataFusion's default 100 GB spill cap, which is now raised to 500 GiB. Query failures are reported in the job summary and do not fail the job.
Changes
vortex-datafusion: default the scan concurrency to 1 under DataFusion. A Vortex scan runsconcurrency * available_parallelismsplits per stream (vortex-layout/src/scan/repeated_scan.rs), and DataFusion opens one stream per partition, one per core by default, so Vortex's default of 4 multiplied with it: ~37k decoded splits in flight on 96 cores.perfshowed the memory was decoded output (to_arrow_decimal,take_views). At SF1000 the fix cut Q1 from 121 to 32 GiB and Q5 from 146 to 52 GiB, sped Q5 and Q8 from ~18s to ~12s, and let Q9 complete.vortex.scan_concurrencystill overrides it.datafusion-bench: opt-inDATAFUSION_BENCH_MEMORY_LIMIT_GIB(aFairSpillPoolbehindTrackConsumersPool),DATAFUSION_BENCH_SPILL_DIRandDATAFUSION_BENCH_MAX_SPILL_GIB. All are unset by default, so existing benchmarks are unchanged..github/workflows/tpch-sf1000.yml: weekly (Saturday) andworkflow_dispatch(engine, scale factor, machine type, queries, iterations, per-query timeout). A build job, then onec8gd.metal-24xlper engine.tpchgen-cli3.0.0 (ZSTD(3), ~1M-row row groups); the in-repo generator writes each table single-threaded, which takes hours for SF1000 lineitem. It uses the sametpchgen-arrow3.0 schema.data-genthen converts to Vortex.prefer_hash_join=false, an 80 GiB memory limit and a 500 GiB spill cap (variantdatafusion-spillin the summary).systemd-oomd, which killed the whole runner cgroup (and lost the job's artifacts) on an out-of-memory query.scripts/bench-memory.py(stdlib only):monitorsamples system memory, page cache, swap, memory pressure and the largest process, tagged with the current phase.run-queriesruns each query in its own process with a raised OOM score, a timeout, and a watchdog that kills the query when available memory drops below a threshold, so the runner survives. Each query gets a status (ok,oom-killed,resources-exhausted,alloc-failed,timeout,error,skipped), wall time and peak RSS fromwait4.--variantlabels a differently configured run of the same engine.summarizerenders the job summary.Data generation and conversion take ~2.5h (Vortex conversion of the single-file lineitem dominates); hash-join queries take ~10 min per engine. Results are uploaded as artifacts and not yet ingested into the benchmarks database.
Open follow-ups: DataFusion Q2, Q11 and Q16 still peak 5–7× higher on Vortex than on Parquet with identical plans (suspected: selective filters keeping whole decoded string buffers alive in hash join builds), and Vortex is ~27% slower than Parquet on DuckDB at SF1000 (Q5 and Q10 ~2×).
API Changes
vortex-datafusion: whenscan_concurrencyis unset, Vortex scans under DataFusion now use a per-core concurrency of 1 instead of the Vortex scan default of 4. Setvortex.scan_concurrencyto restore the old behaviour.🤖 Generated with Claude Code
https://claude.ai/code/session_01FDifRZhxcVd5gvt3dsDhu5