[codex] Prototype frontier scans and compare DuckDB against V1 - #9884
Draft
joseph-isaacs wants to merge 75 commits into
Draft
joseph-isaacs wants to merge 75 commits into
joseph-isaacs wants to merge 75 commits into
Conversation
Adds `vortex-morsel`, the P1 spine of the morsel-based plan execution design: stateful per-morsel `ExecNode` state machines driven inline off one atomic morsel cursor, with a scheduler-visible IO plane that nodes name reads against but never read through. - `ExecNode` is `next_plan` / `execute` / `retire`. Planning names IO as `IoUse`s keyed to whole stored units and is budget-bounded and resumable; execution may only wait on tickets its own planning stream emitted. - Five operators: FLAT, CHUNKED, STRUCT, CONJUNCT (cascade and parallel as one node behind a policy flag) and FILTER. - Nodes live in a per-thread arena instantiated once from a shared immutable plan, so nothing is allocated per morsel and nothing on the hot path is shared between threads. - A per-thread decoded-chunk cache means a chunk straddling several morsels is decoded once rather than once per morsel that touches it. - Unsupported layouts and expression shapes are build errors, never silent fallbacks, so an unsupported query cannot be timed as if it had run. Correctness is differential against the V1 `LayoutReader`: 16 tests covering misaligned chunking (including the design document's [0,3,10) vs [0,6,10) case), thread counts, morsel sizes, conjunct policy and decode-cache budget, each asserting the same rows in the same order as V1. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Adds the comparison harness, the evaluation binary and the P1 findings. The harness makes V1 both a row in the matrix and the oracle: every configuration's output is validated against V1's (equal row count, equal ordered content) before anything is timed, so a configuration that drops rows is reported as a failure rather than as a fast run. Timing then alternates the executors over five iterations and reports the median. Because the named suites need multi-gigabyte downloads this environment cannot fetch, the workloads are shape-matched synthetic fixtures reproducing what the plan says those suites lower to: struct-of-chunked-flat columns whose per-column chunk boundaries deliberately disagree, under conjunctive filters of varying selectivity. This is stated in the findings rather than implied by the numbers, along with the rest of what this run does not establish. Measured over 15 queries, all 105 configuration-query pairs matching the oracle: geomean 0.553 vs V1 at equal thread count, 0.251 at four threads with coalesced morsels. The decode cache accounts for most of the string-heavy win; morsel coalescing accounts for most of the wide-table win. Gate E1 as specified was NOT evaluated: it requires rows B and C (the self-paced graph/reactor and pipeline executors), and neither exists at any commit reachable in this repository. The findings say so explicitly and do not compare a synthetic-fixture ratio against the recorded real-suite ratios. Also carries the scan execution model design documents onto this branch, unchanged from the design branch, so the findings' references resolve. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
…not have The prototype carried a per-thread decoded-array cache and retained resolved IO cells across morsels. V1 has neither, so timing against it with them was measuring a cache, not an executor. Both are removed: there is no decode cache, and a morsel's IO cells are released at retire, so a segment straddling two morsels is read and decoded once per morsel exactly as V1 does per evaluation. Within-morsel cell sharing remains — two uses naming the same stored unit resolve to one read — which is the registration mechanism itself, not retained state. The eval counters now show requests = uses = decodes in every configuration, which also removes the previous 4-thread byte-duplication asymmetry: the cold-scan IO invariant holds at every thread count. Re-measured, all 90 configuration-query pairs matching the V1 oracle: geomean 0.650 vs V1 at equal thread count (was 0.553 with the cache), 0.238 at four threads with coalesced morsels. The string-heavy select-all moves from 0.49 to 0.97 — the honest number for a decode-dominated query — and the delta is recorded in the findings as the measured value of cross-morsel decode reuse (~2x), to be rebuilt in P2 as a shared, budgeted, scheduler-visible facility rather than executor-private state. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Rebuilds the ~2x decode-reuse win on misaligned string layouts as the P1 slice of P2's keyed cells, with retention derived from demand rather than from a budget. Before the scan starts the driver counts, from the morsel cut and the plan's flat nodes alone, how many (node, morsel) pairs will touch each stored unit. The first morsel to decode a unit publishes the array into its cell; every retiring morsel releases its lease whether it used the cell or not; the last release drops the array. Nothing is held speculatively, nothing has an eviction policy, nothing survives the scan, and the ledger is asserted to drain to zero. A morsel whose planning finds the cell populated skips issuing the read — its own unreleased lease pins the value until it retires — so requests fall together with decodes. The cell map is sharded 16 ways: a first cut with one mutex made the wide-numeric workload slower at 4 threads than at 1 (lease traffic scales with nodes x morsels), and sharding restored the scaling. Recorded in the findings as an early admission-plane lesson. Sharing can be disabled (`with_share_decodes(false)`), leaving no state across morsels at all; that configuration is kept as the state-for-state fairness row against V1 and as the chaos check: per query, a sharing run's decodes + reuses must exactly equal a non-sharing run's decodes, asserted in tests along with each straddled chunk decoding exactly once per scan. Re-measured, all 105 configuration-query pairs matching the V1 oracle: geomean 0.539 vs V1 at equal thread count (0.644 with sharing disabled), 0.249 at four threads with coalesced morsels. String-heavy select-all: 310 decodes without sharing, 121 with — one per chunk — moving 0.87 to 0.46. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Replaces the shape-matched synthetic TPC-H rows with the real thing. Data comes from tpchgen at a real scale factor — dbgen's schema, distributions and correlations, decimal(15,2) money, date[days] columns — imported through the session's own Arrow path, the same one vortex-bench uses to build its TPC-H files. No download required. The fixture is written through a real btrblocks compressing pipeline (repartition 8192 rows, coalesce to 1 MiB, compress, buffer, chunk, flat), so decode cost is what a real file imposes rather than the raw-buffer cost the synthetic fixtures had. Zone maps and the dictionary layout are left out because P1 supports neither: enabling them would compare a pruning executor against a non-pruning one instead of comparing executors, which is stated as a capability gap rather than folded into a ratio. Queries are the scan portion of the TPC-H queries in vortex-bench/sql/tpch/ — Q6, Q1, Q14, Q15, Q12, Q19 — transcribed predicate for predicate, plus a bare six-column scan and a highly selective shape. The exactness contract is stricter than the synthetic eval's: every configuration must reproduce V1's dtype, row count and ordered content before any timing, and a mismatch aborts the run rather than dropping a row from the table. Also stops registering single-lease units in the shared cells. A unit touched by exactly one (node, morsel) pair can never be reused, and registering it cost ~20% on a pure six-column scan for bookkeeping that could never pay off. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
The prototype is ~1.3x faster than the V1 LayoutReader at one thread and ~1.5x at four (1.85x with coalesced morsels), on real TPC-H lineitem with real decimals, dates and btrblocks encodings. All 48 configuration-query pairs reproduced V1's dtype, row count and ordered content exactly. Two results that revise the earlier synthetic findings, both recorded rather than smoothed over: The single-thread margin drops from 0.54x to 0.75x. The P1 findings predicted exactly this: writing uncompressed leaves understated decode cost, which both executors share, and so inflated the prototype's apparent margin. Compressed decode now dominates and the margin narrows to what the executor controls. Q6 (0.92x) and Q12 (0.85x) are the clearest cases. Cross-morsel decode reuse turns out to be neutral on a real file (0.75x with sharing, 0.76x without) because the real write pipeline repartitions every column onto the same row blocks, so the misalignment the synthetic fixtures forced does not occur. Q19 is the exception, at 0.54x against 0.69x, because its string columns compress to sizes that land on different block boundaries. Keyed cells earn their place on width-divergent schemas and on filter-and-project column overlap, not as a general win. Also removes a stale mut and refreshes the raw evaluation output. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Adds a sweep mode to tpch-eval that measures rather than asserts why the four-thread rows win, and a handoff document for re-running the whole evaluation on other hardware. The sweep varies three things: the morsel driver's thread count from one to four times the core count, V1's per-worker split concurrency (so the baseline is tuned to its own best rather than left at a possibly unfavourable default), and morsel size. Against V1 tuned to its best concurrency the morsel executor at four threads is 0.61x geomean, decomposing as ~0.73x single-thread base advantage times ~1.19x better scaling — roughly three quarters base, one quarter scaling. The win is not oversubscription. This host has one thread per core, and one driving thread per physical core is optimal on seven of eight queries (x8 costs ~10%, x16 ~20%). V1 reaches its best at four workers times concurrency sixteen, so 64 in-flight split tasks against the morsel driver's four morsels: the driver needs 16x fewer concurrent units and still wins. Corrects an overstatement in the previous commit. Morsel coalescing does not generally help: excluding Q19 it is 1.02x, no effect at all, and the whole geomean advantage of the 64k row was Q19 alone (0.42x), whose string columns land on different block boundaries and give it 366 natural splits where every other query has 92. Past 64k rows coalescing hurts, with Q12 going from 9.3ms to 22.7ms at 1M-row morsels. Also records that single-query differences under ~20% are within this host's noise, which is one of the reasons the handoff asks for a re-run elsewhere. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Keep one thread-local morsel and reusable arena per worker while sharing required and speculative IO queues with exact-ticket wakeups. Move file reads into planning and the background service, improve local coalescing, add hot/cold and morsel-size benchmark controls, and record the validated TPC-H results. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Add the morsel executor, push-read scan integration, per-morsel statistics pruning, compression and random-access runners, persistent worker optimizations, and loadable DuckDB extension benchmark support. Layout-v27 and AVX-512 experiments are intentionally excluded. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Source-tree: ae8b9800409a60d1ceebb2b8181a144581a0cc45:vortex-morsel Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Own plan caches per fixed-layout executor to remove stale global pointer identity and retain reusable plans safely. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Add mask-aware morsel planning, persistent workers and arenas, batched priority-aware buffered I/O, safe reopen plan reuse, dictionary and nullable layout support, and opt-in scan observability. Document the retained design, rejected experiments, cache boundaries, reproduction workflow, and final SSD comparison against V1. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
…morsel-perfect-io-scheduling Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> # Conflicts: # Cargo.lock # Cargo.toml # vortex-bench/Cargo.toml # vortex-bench/src/random_access/take.rs # vortex-io/src/read_at.rs # vortex-layout/src/segments/source.rs # vortex-morsel/Cargo.toml # vortex-morsel/README.md # vortex-morsel/src/bin/tpch-eval.rs # vortex-morsel/src/build.rs # vortex-morsel/src/driver.rs # vortex-morsel/src/harness.rs # vortex-morsel/src/io.rs # vortex-morsel/src/lib.rs # vortex-morsel/src/node.rs # vortex-morsel/src/nodes/chunked.rs # vortex-morsel/src/nodes/conjunct.rs # vortex-morsel/src/nodes/flat.rs # vortex-morsel/src/nodes/mod.rs # vortex-morsel/src/nodes/struct_.rs # vortex-morsel/src/stats.rs # vortex-morsel/src/tests.rs
…io-scheduling Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> # Conflicts: # scripts/random-access-split.py
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Signed-off-by: EC2 Default User <ec2-user@ip-172-31-75-25.ec2.internal>
Drop the ScanExecutor hook from vortex-layout's ScanBuilder and give the morsel executors their own builder in vortex-morsel-scan. The builder mirrors the ScanBuilder surface but never constructs a LayoutReader: layout planning and predicate execution belong to the selected morsel implementation. DataFusion and DuckDB pick the builder by backend and only build a LayoutReader for the V1 path. Both executors expose full_file_splits and build one output future per morsel, completed through a sink as each morsel retires. The pull driver keeps a filtered-scan lookahead window ahead of the active workers; the push driver issues lookahead batches through request_background_batch and can issue every source in the window eagerly. duckdb-bench links the workspace extension in-process when no loadable extension is configured, and falls back to the library vortex-duckdb built when DUCKDB_LIB_DIR is unset. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NAzndXAjqTKVjgcSbiYyDo
Plan execution no longer owns a segment source. MorselScan::new takes a plan and a session; the reads its scheduler wants started leave the scan as IoDemand on a stream taken with take_io (Start batches that preserve the planning-wave boundary, and Promote when execution blocks) and are answered through IoCompletions. Workers park on their exact cells and never poll a storage future; wait accounting moves into the service. SegmentSourceDriver serves that demand from any SegmentSource, either as a task on a runtime handle or on a dedicated thread for runtime-less callers. It registers each batch together so coalescing sources see neighbours, polls required and promoted reads immediately, and polls speculative reads through a bounded window so a demand-driven source's promotion order stays meaningful. The non-blocking inline probe survives as an optional hook the driver installs from the source's request_nowait. Both executors connect the driver outside the scan. The push crate drops its urgent and ready I/O work queues and the wakers that requeued them; the DuckDB current-thread path shares one service and driver across a file's morsels. Benchmarks, harnesses, and tests connect the driver the same way. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NAzndXAjqTKVjgcSbiYyDo
Driver: remember a promotion that arrives before its own start batch so a worker blocked on a read another worker just started is still served first; count the speculative window separately from required reads; fail the reads a segment source returns no future for instead of leaving them outstanding. Service: when the demand stream is closed, fail the affected cells and wake their waiters so the scan errors instead of parking forever, and refuse to run a scan whose demand was never taken. Skip the separate promotion when a read just went out as required. Document io_waits and io_wait_time as completion-based. Executors: apply an unfiltered LIMIT to the morsel cut before any I/O, and stop assigning morsels once every consumer has dropped its future. The push scheduler again attributes planning-wave starts to their morsel so the per-morsel request counters are meaningful. duckdb-bench prefers the DuckDB library vortex-duckdb was built against. Add docs/developer-guide/internals/scan-execution-models/morsel-executor-primer.md: each trait and type simplified, one scan end to end, and what the experiment taught. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NAzndXAjqTKVjgcSbiYyDo
Every output future now carries a guard; when the last one is consumed or dropped the scan is cancelled through ScanCancellation (pull) or StreamCancellation (push), which stops the scheduler and wakes parked workers, so a scan stalled on slow reads no longer outlives its consumers. The completion-sink cancellation check is gone with it. An unfiltered limit is now exact at the executor: morsels past it are never read and the last one is capped, and a zero limit builds no tasks. Filtered scans still return every matching row for the builder to trim. Two tests cover the limit cut and cancellation of a stalled scan; the primer's limit rule matches the implementation. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NAzndXAjqTKVjgcSbiYyDo
An already-cancelled scan no longer submits its startup lookahead or refills it, so a query cancelled before its coordinator starts issues no I/O. A cancelled pull scan leaves the leases of unfinished morsels outstanding by design, so the lease-ledger assertion only runs when every morsel retired; the cancellation test now keeps decode sharing on. The DuckDB current-thread path applies the same exact row caps as the shared path when an unfiltered limit ends inside a morsel. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NAzndXAjqTKVjgcSbiYyDo
…werer traits Three extension points of the pull executor become traits, each with a reason the primer records: LayoutPlanner turns one kind of stored layout into nodes. It owns both answers about its layout, where chunks start and which nodes execute it, so the morsel cut and the plan agree by construction. The built-in planners (zoned wrappers, flat, dictionary, struct, chunked) move to layouts.rs, and LayoutPlanners is an ordered registry a caller can extend ahead of them. This replaces two duplicated layout matches in build.rs. NodeBlueprint is the immutable half of a node, instantiated once per worker arena. Planners push blueprints and only stored_use is inspected from outside, so a planner can introduce node types the scheduler has never seen. The closed NodeSpec enum is gone; each nodes/*.rs file now holds its spec next to its exec. IoAnswerer is whatever serves the demand stream. MorselScan::connect and connect_on_thread attach any answerer; SegmentSourceDriver is one of them. IoPlane::ready no longer hands a read out itself on a probe miss, since the scheduler already does so when it parks the worker. Two tests register a planner ahead of the built-ins and serve a scan from an answerer with no SegmentSource. The primer gains a section on which extension points are traits and why the rest stay concrete. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NAzndXAjqTKVjgcSbiYyDo
Represent logical I/O groups with an incremental plan cursor that can move across groups and row ranges without materializing a range-by-group matrix. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Consume grouped plan frontiers with bounded lookahead, exact raw-cell leases, speculative-read cancellation, and owned driver teardown. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Exercise frontier group shapes, scheduling depths, pruning, retention, cancellation, shared cells, and scan teardown. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Add push and V1 discard-output timings, frontier configuration matrices, hot and cold disk controls, injected latency, raw retention metrics, and reusable SF10 packs. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Document the grouped frontier design, implementation caveats, benchmark history, validation state, and handover instructions. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Keep V1 on the current LayoutReader scan defaults while selecting grouped frontier I/O for ordinary push rows. Label both paths explicitly and test that the frontier benchmark defaults change only the scheduler selector. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Use one additional down frontier per worker in the production push-frontier policy while keeping right speculation disabled and 32-range refills. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Restore the production push-frontier policy to zero additional down lookahead after the clean interleaved TPC-H Q6 A/B produced conflicting internal and supervisor evidence. Preserve the trial attestation in the benchmark checklist. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Use 512K-row morsels for the production push-frontier route while preserving the plain push executor fallback and explicit benchmark controls. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Restore the 128K-row production frontier target after the all-core Q6 trial showed no robust speedup and a consistent RSS penalty. Record the clean build, exact-result, ordering, and normalization evidence. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Retain opt-in projection admission and conversion-cache improvements, add persistent real-file benchmark drivers, and compare V1/W8/prefetch on independently validated TPC-H SF10 and full ClickBench data in CI. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk>
Normalize recognition of existing sign-offs with quoted author names. No source files or historical commits are changed. I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 57ff482 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: cca7f7a I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: dabd419 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: f22008b I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 27cf7d4 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: e9fd51f I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 6867f49 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 287d005 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: d7cf588 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: bd1f3cb I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 381487e I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 4934e3b I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 2c3f7f8 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 442e385 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 714d20e I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 7e64ed6 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: fa9dd59 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: cb2dddb I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 08d146a I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 2022bd5 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: c69bd62 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 0b1eef5 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 9fc6989 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: ade2bee I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 6d08d4f I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: eab26ff I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 10e6135 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: ae8c638 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 568fcb5 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 4bae722 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: f102ab2 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 121a045 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 761ff9f I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: d25daa1 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: fbb232a I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: f2822e7 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 7b0b268 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: d36cd71 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 58e2012 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 104297a I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: 8295653 I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: ad0749e I, Joe Isaacs <joe.isaacs@live.co.uk>, hereby add my Signed-off-by to this commit: e199904 Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk> Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This draft introduces the grouped-I/O frontier scan prototype and its integration with DuckDB. It retains the real V1 path for direct comparison and keeps frontier pairing and projection prefetch opt-in.
The frontier executor groups reads according to predicate/projection demand, shares file-level I/O admission, and bounds speculative work. DuckDB's external execution path owns its output work and conversion caches. This branch also contains the supporting scan abstractions, diagnostics, tests, and experiment history; it is a broad prototype draft rather than a production-default change.
The latest local comparison on real TPC-H SF10 files, at 14 threads, reduced the sum of all 22 query medians from 2947.30 ms with V1 to 2497.73 ms with W8 plus projection prefetch (15.25%). Projection prefetch contributed 2.24% beyond W8. Q6 regressed with prefetch and Q9 was flat, so prefetch remains disabled by default. These are local Apple Silicon results, not CI results.
The earlier local ClickBench timing campaign was excluded after unrelated IDE/Cargo activity contaminated it. The completed dedicated CI comparison below now provides a valid real-file ClickBench result. Earlier diagnostics show that the W8 per-file pairing threshold does not activate for the small shards tested; these gains describe the full frontier implementation, not pairing alone.
CI comparison
The
pr-frontierpreset of PR SQL Benchmarks builds the comparison drivers and runs TPC-H SF10 and the full 100-shard ClickBench dataset sequentially on a dedicated bare-metal runner. Each suite compares V1, W8, and projection prefetch with one binary, 14 threads, six balanced rounds, and an immediate same-arm warmup per measurement. It checks independent Parquet results, identical V1/frontier plans, result cardinality, file/binary/SQL hashes, and no spilling. Per-query tables, paired-round intervals, exact commands, raw results, and operator profiles are uploaded as workflow artifacts.SQL comparison run 34962772259 completed successfully on 2026-09-15. It tested commit
e199904d7a57b88312ecbc880eab0ec07edbe79b; the subsequent DCO-only commit has the identical source tree (a4d9bf0dd016ccb096d5dda64b8c4bbbeae811fe). Hardware: dedicated AWS c6id.metal, Intel Xeon Platinum 8375C, DuckDB 14 threads, CPU/memory affinity to NUMA node 0.These are full-query elapsed times with warm real-file scans. Totals are sums of per-query medians, not scan-only time or one timed suite execution.
Both fixed frontier configurations beat V1 in aggregate on both suites. Prefetch reduces TPC-H time by a further 2.30% versus W8, but is 0.32% slower on ClickBench; its paired interval there crosses parity, so there is no demonstrated ClickBench prefetch benefit. Prefetch remains opt-in.
The six paired round totals favor frontier in every round for both configurations and both suites. Exploratory 95% bootstrap intervals for frontier/V1 total-time ratios are TPC-H W8 [0.8673, 0.8778], TPC-H prefetch [0.8467, 0.8565], ClickBench W8 [0.8362, 0.8441], and ClickBench prefetch [0.8370, 0.8472]. These quantify within-run repeatability, not variation across machines or independent CI runs.
TPC-H has lower medians on 20/22 queries in each frontier arm; Q2 and Q16 regress by up to 2.27%. ClickBench has lower medians on 43/43 queries in each arm, although small differences such as W8 Q39 (+0.30%) are not established individual wins. TPC-H Q9 remains a modest 5.89% reduction with prefetch. ClickBench Q28 remains dominant and improves only 1.82% with W8. No per-query multiple-comparison correction is applied.
Validation completed 1,170 measured executions, 2,535 warm/measured/validation checks, and 195 separate no-spill profiles. All 65 queries passed independent Parquet-reference validation for all three Vortex arms. Post-download auditing rechecked 2,535 result hashes and 2,031 full-value comparisons (the other 504 canonical executions have ambiguous LIMIT ties and use row-count checks), all plan-equality gates, measurement order, recomputed medians, source identity, and profile spill counters.
CI input sizes: TPC-H Vortex 2,379,939,292 bytes across eight files (59,986,052 lineitem rows); ClickBench Vortex 11,616,661,328 bytes across 100 files. Each arm used exactly the same files; manifests include their SHA-256 hashes, SQL, binaries, and linked DuckDB. CI-generated TPC-H compression differs from the earlier local archive, so absolute local and CI timings are not directly compared.
Download all raw results, manifests, profiles, and complete 65-query tables (artifact
duckdb-frontier-results, archive digestsha256:05c6d68f33ba843fda4e63d8cfcf26fded6dd5276f814b3b2e1d815053557b62). Exact CI binaries are also available.The comparison reports improvements and regressions and does not require a speedup to make the job green. The runner's optional global workqueue CPU-mask write was rejected by sysfs; process NUMA affinity was recorded successfully. The runner's post-job metrics visualizer also failed after results were uploaded. Neither is hidden as a successful check.
Known limitations
The draft currently conflicts with
develop. DCO formatting was repaired without rewriting history; older commit4974fd147d7492b57d37702b90ec1b3c03fa0bb5still needs author attention because its signer isEC2 Default Userrather than its author. These do not prevent the explicit branch-head benchmark run.TPC-H Q22 has an unresolved default-plan correctness bug shared by V1/frontier. The comparison disables
common_subplanonly for Q22, equally in every arm and the Parquet reference. This benchmark is not a claim that the default Q22 path is fixed.ClickBench's canonical SQL is timed unchanged; correctness uses separate tie-breakers for ambiguous LIMIT queries, including the Q24 and Q30 ties found during this work. Floating aggregates use a tight numeric tolerance; other values and schemas are exact.
The W8 policy is experimental: SQL LIMIT propagation and memory/resource acceptance remain open. There is no claim here about cold-cache or remote-storage performance.
Historical handover links under
/private/tmpidentify local evidence archives. New CI artifacts provide downloadable evidence for the exact PR head.Validation
compress-bench/src/main.rs:408cognitive-complexity error (30 versus 25); it is not hidden by this draft.The canonical implementation/experiment notes are in
vortex-morsel-push/IO_FRONTIER_HANDOVER.md.