Repository navigation
Description
Intent
Cut the memory-latency cost of Ravel's heavy GROUP BY statements. The hash-aggregation final stage spends most of its time stalled on random DRAM accesses into group tables far larger than cache. This epic tests whether a cache-aware final aggregate (radix or hash buckets aggregated one at a time, after the PivotLake argument and apache/datafusion#25724) cuts server CPU on the aggregation cohort without an unaccounted rise in memory. It also tests whether exact per-bucket top-k (#1402) lowers peak intermediate bytes.
The epic changes production behaviour only once a controlled A/B clears its pre-registered bands. Until then any new path stays behind a switch that is off by default.
Claim and split from #3231: comment 6548900176. This epic consumes dfbench-20261031T2032Z (#3231 comment 6548777041) as its component gate for bucketing and does not re-run it.
Stage 0 numbers
These are prior figures, taken before #3516, #3520, #3524 and #3549 landed. The current-main baseline below replaces them once it reads out.
From prof028-20261030T1244Z at b48b765 (#3231 comment 6525280440). Shares are of server CPU per statement, operator view:
| stmt | hot s | server CPU-s/call | aggregate operator share | hashing + group-key table share |
|---|---|---|---|---|
| q33 | 3.511 | 50.28 | 68.0% | 44.6% |
| q35 | 3.069 | 45.38 | 47.2% | 28.9% |
| q34 | 3.052 | 45.08 | 47.7% | 29.3% |
| q19 | 2.488 | 35.60 | 54.6% | 44.3% |
- Final-table find and insert on the six heaviest statements: 4.18 s of the 38.69 s hot sum (Epic: ClickBench hot geomean on the benchmark image: the per-statement floor, then the string and decode paths #3231 comment 6542264518, section 3).
- q33 peak intermediate bytes: 11.8 GB (prof028 answers).
- No hardware counters exist yet. Every memory-stall claim so far is an inference from leaf frames (
_mm_movemask_epi8under the probe, stores underinsert_accounted).
Current-main baseline: pending, pre-registered on this issue before launch.
Measurement command: hot-path-artifacts/launcher/launch.sh <run-id> <main-sha> cpu (the #3231 bench-box launcher, c6a.4xlarge, ClickBench f1ef35e6).
Tasks
- Factor-free peakAskedIntermediateBytes in the SQL query stats #3607 T0 factor-free
peakAskedIntermediateBytesSQL stat (ravel-sql, ravel-server; low) - BucketedFinalAggregateExec behind sql_bucketed_final_aggregation (ADR-3565) #3608 T1
BucketedFinalAggregateExec+ rule + config + server flag + tests (ravel-sql, ravel-server; high) - Exact per-bucket top-k in BucketedFinalAggregateExec (ADR-3565) #3609 T2 exact per-bucket top-k (ravel-sql; medium)
- Bucketed final: charge the top-k transient, nullable-key and below-threshold top-k tests #3639 follow-ups from the BucketedFinalAggregateExec behind sql_bucketed_final_aggregation (ADR-3565) #3608/Exact per-bucket top-k in BucketedFinalAggregateExec (ADR-3565) #3609 checkpoint reviews (charge the cut transient, nullable-key and below-threshold top-k tests, preserve_partitioning case, chunk default) (ravel-sql; low-medium)
- step 3: paired
cpuabA/B on the reference machine, bands from ADR-3565 decision 6, pre-registered here before launch (orchestrator, not fleet)
Ledger
Stage 0 - running
-
2026-10-31 20:49Z: pre-registered (comment 6548953902), launched
pmuX-20261031T2049Z(c6a.4xlarge, i-0e958511477e12efb) andpmuA-20261031T2049Z(c8g.4xlarge, i-08847b6a2010ca942), both at main 000557f, stagepmu. -
Waiting on
dfbench-20261031T2032Z(Epic: ClickBench hot geomean on the benchmark image: the per-statement floor, then the string and decode paths #3231 comment 6548777041) as the component gate for bucketing. -
21:05Z:
dfbench-20261031T2032Zis INVALID (A/A spread in growth mode; Epic: ClickBench hot geomean on the benchmark image: the per-statement floor, then the string and decode paths #3231 comment 6548915738). The re-run,dfbench2-20261031T2046Z, is the gate. Directional only: V1 bucketing on s33 0.657, s17 1.363, s19 1.149, s34 1.392. c6a.4xlarge hides the PMU (cyclesnot supported), sopmuA(c8g) is the counter source. -
Track split agreed with the Epic: ClickBench hot geomean on the benchmark image: the per-statement floor, then the string and decode paths #3231 session: this epic owns the bucketed final exec. Epic: ClickBench hot geomean on the benchmark image: the per-statement floor, then the string and decode paths #3231 keeps the dfbench readouts, the prefetch (V2) result and R1.
-
21:40Z:
pmuADONE but recorded no counters (stale probe file, launcher bug, fixed). Group counts and VmHWM usable: q33 has 99,997,493 groups = rows, 118 B/group. RelaunchedpmuA2-20261031T2137Z(i-0c432c9319c651462) at 000557f.pmuXstill running. -
21:55Z:
pmuXDONE, INVALID for timing (host ran every statement 1.43x slow; same-build boxes same day sit at 1.05). Memory rows inside bands as observations: q33 VmHWM 16.0 GB, 118 B/group. Readout comment posted. Waiting onpmuA2for counters. -
22:30Z:
pmuA2DONE, INVALID (16-event multiplexing 7-19% < 25%; fill event ll_cache_miss_rd reads 0 on Graviton4). Observations: q33 memstall/cycles 0.386, IPC 2.10 (IPC bands wrong basis for 8-wide V2). RelaunchedpmuA3-20261031T2227Z(i-0f03ae6a7656081d1), 6 events, fill=cache-misses. -
16:26Z:
pmuA3DONE, VALID (checks 1,2,4 pass; 6 events at 29-49%). q33 memstall/cycles 0.355 (P5 PASS), fill samples: final probe 0.40 (P7 just under band), repartition scatter 0.36 (unregistered), create_hashes 0.007 (P8 PASS); q34 P9 0.249 MISS (decode-led). IPC bands P3/P4 retired (8-wide core). Readout comment 6560647859. Stage 0 complete; ADR-3565 goes to the owner gate. -
16:53Z: ADR-3565 draft reviewed adversarially, round 1 BLOCK (memory band on a factor-biased ledger figure; top-k reach rule never matched the plan shape), round 2 PASS; all findings applied, memory gate moved to a factor-free asked-bytes stat PR1 adds. Review note comment 6560956726. Draft at the owner gate.
Stage 1 - ADR approved
- 2026-11-01 17:02Z: owner approved ADR-3565 (fused routing stays a later amendment). Committed in worktree store-wt-3565, PR docs: add ADR-3565 bucketed final aggregate for fixed-width keys #3606 open, fleet review requested. Stage 2 decomposition starts while the review runs.
Wave plan (Stage 2)
| ID | ticket | crates | predicted files | deps | acceptance test | risk |
|---|---|---|---|---|---|---|
| T0 | #3607 | ravel-sql, ravel-server | crates/ravel-sql/src/memory.rs, crates/ravel-sql/src/executor.rs, services/ravel-server/src/query.rs, services/ravel-server/src/sql.rs, services/ravel-server/src/exemplars.rs, docs/query-engine.md | none | memory.rs::peak_asked_intermediate_bytes_ignores_the_reserve_ahead_factor; ravel-server sql_stats_json_carries_peak_asked_intermediate_bytes | low |
| T1 | #3608 | ravel-sql, ravel-server | crates/ravel-sql/src/{config.rs,session.rs,lib.rs,bucketed_final_aggregate.rs}, crates/ravel-sql/tests/{bucketed_final_aggregate.rs,ceiling_breach.rs,memory_accounting.rs,memory_ceiling_and_budget_errors.rs,pushdown_memory.rs,scan_budgets.rs,spill.rs}, services/ravel-server/src/{config.rs,query.rs,lib.rs}, services/ravel-server/tests/logs_fetch_policy_e2e.rs, docs/query-engine.md | T0 | tests/bucketed_final_aggregate.rs::answers_match_the_stock_final_at_1_4_and_32_partitions; query.rs::sql_bucketed_final_aggregation_is_reachable_from_cli (reachability) | high |
| T2 | #3609 | ravel-sql | crates/ravel-sql/src/{compact_slices.rs,bucketed_final_aggregate.rs}, crates/ravel-sql/tests/bucketed_final_aggregate.rs, docs/query-engine.md | T1 | tests/bucketed_final_aggregate.rs::per_bucket_topk_is_exact_on_ties | medium |
Waves: 1 = T0; 2 = T1 (solo, effort high); 3 = T2. Each wave dispatches from main after the previous wave and #3606 have landed. Step 3 A/B follows wave 3; a step-3 miss keeps the switch off and the epic closes with the benchmarks and findings only.
-
Factor-free peakAskedIntermediateBytes in the SQL query stats #3607 task=fb395810-0d79-4dc3-8149-5bc12db19c31 dispatched
-
Factor-free peakAskedIntermediateBytes in the SQL query stats #3607 task=cf871352-c4b6-4589-b98f-f2542ea8a6d3 dispatched
-
19:17Z: T0 Factor-free peakAskedIntermediateBytes in the SQL query stats #3607 task cf871352 done, result 9d1f526 (executor pmoust-mbp; main suite + sql/flight-sql/esql/agents-mcp lanes green by hand, 4 untouched lanes not run). Checkpoint review running. First attempt fb395810 died at the disk guard on ci-16gb-fsn1-1.
-
19:26Z: checkpoint review of T0 result 9d1f526: BLOCK, 1 block (asked gauge drops held shrinks: it is the live-reservation peak, not charged/1.25; docs and the server test lower bound asserted the latter), 5 should-fix, 3 nits. Decision: keep the live-reservation meaning, fix prose/tests. Fix round dispatched from landing commit (land/3607, re-authored). PR docs: add ADR-3565 bucketed final aggregate for fixed-width keys #3606 at owner merge decision after 3 review rounds.
-
Factor-free peakAskedIntermediateBytes in the SQL query stats #3607 task=60c503dc-1a1f-4f62-9fa5-8a28d7a348e0 dispatched
-
20:18Z: fix-round task 60c503dc (ravel-clickbench-ubuntu-2, 50 min) died with no commit: it ended its turn waiting on a backgrounded cargo test while trying to force asked < charged on the small e2e fixture. Redispatching with a factor-1.25-vs-OFF in-process test design and commit-first ordering.
-
Factor-free peakAskedIntermediateBytes in the SQL query stats #3607 task=2179de5f-7c36-4368-935a-10d342db01ce dispatched
-
21:13Z: fix round 2 task 2179de5f done (result 8861684, 6 wip commits; gate auto-backgrounded, PR CI is the gate). Folded into one fix commit on land/3607 (re-authored), PR feat(sql): report the pre-reserve-ahead peak in the SQL stats #3621 opened (Fixes Factor-free peakAskedIntermediateBytes in the SQL query stats #3607), fleet review requested, checkpoint reviewer re-verifying the fix.
-
21:15Z: checkpoint re-review of land/3607 (PR feat(sql): report the pre-reserve-ahead peak in the SQL stats #3621): PASS (opus). All 9 findings fixed or decided; 4 nits left (an "only when" that should be "if", an untested grow-served-from-hold claim, a determinism assumption in the new integration test, HOLD_ALLOWANCE 1.1 underived). Note for the A/B pre-registration: with the live-reservation meaning arm A asked figure can come in under the 9.4 GB estimate; the band reads A from its own stat.
-
21:41Z: PR feat(sql): report the pre-reserve-ahead peak in the SQL stats #3621 (T0): CI 33 pass, fleet review APPROVED (3 nits, answered, deferred to BucketedFinalAggregateExec behind sql_bucketed_final_aggregation (ADR-3565) #3608 which touches the same files), checkpoint PASS. Shape-guard hits verified false positives (return type, unrelated run()). Enqueued to the merge queue pinned to 5c9bacc. T1 dispatches once it lands.
Wave 1 - landed
plan: T0 #3607 (from main eccc364)
T0 #3607 task=fb395810 dead (disk guard); task=cf871352 done result=9d1f52637; fix round task=60c503dc dead (no commit); task=2179de5f done result=886168434
review: block then pass (opus; 1 block + 5 should-fix fixed in the fix round, 4 nits carried to #3608)
landed: PR #3621 f839749; main=f8397497c; closed #3607
- BucketedFinalAggregateExec behind sql_bucketed_final_aggregation (ADR-3565) #3608 task=4d44de9b-d28a-4f0b-8b44-116c5854e19c dispatched
Wave 2 - landed
plan: T1 #3608 (from main f839749), solo, opus, effort high
T1 #3608 task=4d44de9b-d28a-4f0b-8b44-116c5854e19c dispatched 2026-11-01T22:17Z. docs/query-engine.md kept out of the task (shared with open #3616); the orchestrator adds the section at landing from the report text.
-
01:09Z: T1 BucketedFinalAggregateExec behind sql_bucketed_final_aggregation (ADR-3565) #3608 task 4d44de9b done (ravel-clickbench-ubuntu-2, 2h50m), result e9efe3f, 20 files +2368, gates EXIT=0 on every lane. Executor findings: default chunk slack 8 MiB/partition (exceeds the 256 MiB default query ceiling at 32 partitions; the ADR 0.1 GB estimate is low), transient double charge during engage, peak asked bytes 0.58 of stock at 400k unique keys. Checkpoint review running.
-
01:28Z: checkpoint review of T1 result e9efe3f: PASS (opus; 0 block, 3 should-fix, 8 nits). Should-fix: ADR chunk-slack estimate ~9x low (0.885 GB up front on q33 at defaults; predicted asked B/A 0.665, inside band) -> ADR amendment; default chunk size can refuse small queries under the 256 MiB ceiling -> revisit before the flag is on; no test of a repeated/NULL key present at engage and arriving later -> carried into T2 Exact per-bucket top-k in BucketedFinalAggregateExec (ADR-3565) #3609. Landed as PR feat(sql): add BucketedFinalAggregateExec behind a config field #3628 (feat + docs section commit, re-authored), fleet review requested. Shape-guard hits (14) verified false positives by the reviewer.
-
02:35Z: PR feat(sql): add BucketedFinalAggregateExec behind a config field #3628 (T1): fleet review 3 rounds (round 1 should-fix: guide peak sentence; round 2 should-fix: converted aggregate forfeits the TopK pre-filter until decision 4, now recorded in module doc + guide; round 3 APPROVED, re-posted nits only), CI 36 pass, checkpoint PASS. Enqueued to the merge queue pinned to b4d0ce0. Nits carried into Exact per-bucket top-k in BucketedFinalAggregateExec (ADR-3565) #3609: Decimal128/Date32 routing shape, Router per-batch alloc, Drain yield on empty buckets. T2 dispatches once it lands.
T1 #3608 task=4d44de9b done result=e9efe3fb2
review: pass (opus; 3 should-fix recorded: ADR chunk-slack estimate, default chunk size vs ceiling, missing engage+later-row test -> #3609; fleet review 3 rounds, 2 doc should-fix fixed)
landed: PR #3628 0d5733c; main=0d5733c26; closed #3608
- Exact per-bucket top-k in BucketedFinalAggregateExec (ADR-3565) #3609 task=c33514e9-c01c-4561-9a93-e2f0552d574d dispatched
Wave 3 - landed
plan: T2 #3609 (from main 0d5733c), solo, opus, effort high
T2 #3609 task=c33514e9-c01c-4561-9a93-e2f0552d574d dispatched 2026-11-02T03:01Z. Carries the engage-plus-later-row test and the three #3628 nits.
-
05:44Z: T2 Exact per-bucket top-k in BucketedFinalAggregateExec (ADR-3565) #3609 task c33514e9 done (ravel-clickbench-ubuntu-2, 2h43m), result e88fbf0, 4 files +800/-40, gates green on every lane. Deviation: the per-bucket cut does not lower whole-plan peak asked bytes (each bucket table is charged in full before its output is cut; measured identical with and without the cut), so the peak-fall test was replaced by a pending-output test; it does cut the sort input to at most 64 x k per engaged partition. Checkpoint review running.
-
05:55Z: checkpoint review of T2 result e88fbf0: PASS (opus; should-fix: uncharged transient during the cut, nullable-key top-k untested, 1-partition legs never fire the rule, preserve_partitioning case swapped; all to Bucketed final: charge the top-k transient, nullable-key and below-threshold top-k tests #3639, transient disclosed in doc + changelog). Landed as PR feat(sql): cut each bucket to its exact top k in the bucketed final #3640 (feat + docs paragraph + disclosure), fleet review requested.
-
06:31Z: PR feat(sql): cut each bucket to its exact top k in the bucketed final #3640 (T2): fleet review round 1 doc should-fix + nit fixed (29cc8b0), round 2 APPROVED; CI red on check-shard 4 only at the runner disk floor (all 3419 tests passed, lowest free 1135 MiB under the 3 GiB floor, the CI check-shard (4) fails the disk floor on ubuntu-latest runners that start with 19 GB free #3564 runner class); assert-green-head recorded the signature and reran once. Follow-ups filed as Bucketed final: charge the top-k transient, nullable-key and below-threshold top-k tests #3639.
-
06:44Z: PR feat(sql): cut each bucket to its exact top k in the bucketed final #3640 CI rerun failed the same way (check-shard 4 disk floor, 1197 MiB; all tests passed). assert-green-head would escalate on the repeated signature; overridden once by hand with the evidence that the previous head passed the same shard (run 41396804546) and CI check-shard (4) fails the disk floor on ubuntu-latest runners that start with 19 GB free #3564 is open: third run issued. Data point left on CI check-shard (4) fails the disk floor on ubuntu-latest runners that start with 19 GB free #3564.
-
06:58Z: PR feat(sql): cut each bucket to its exact top k in the bucketed final #3640 (T2): third CI run green (24 pass), fleet review APPROVED, checkpoint PASS. Enqueued to the merge queue pinned to 29cc8b0. Step 3 (paired cpuab A/B) launches from the landing commit; pre-registration posts here first.
T2 #3609 task=c33514e9 done result=e88fbf0f9
review: pass (opus; should-fix items filed as #3639; fleet review 2 rounds, doc fixes; CI needed 3 runs for the #3564 runner disk floor)
landed: PR #3640 6df1524; main=6df152450; closed #3609
Step 3 - A/B read out
-
07:17Z: pre-registered (comment 6571398126), launched
bfab-20261102T0716Z(c6a.4xlarge, i-0da38ead1503067fb, stage cpuab, A = B = main 6df1524, arm B --sql-bucketed-final-aggregation, ABBA x2). Results: s3://ravel-clickbench-5b7101/reports/cold-floor/bfab-20261102T0716Z/. Peer session told; feat(logseg): serve LIKE-deferred columns when the scan fits the cache #3630 base change noted in validity check 4. -
08:40Z: bfab-20261102T0716Z DONE, VALID for every B/A (readout comment 6572771621). q33: CPU B/A 1.000 MISS, wall 1.004 MISS, asked bytes 0.623 PASS (9.48 -> 5.91 GB; VmHWM 18.25 -> 13.65 GB). q05/q09/q16/q36 CPU 0.83-0.92 with asked bytes up 1.1-3.3x (chunk slack); q31 1.03. Hot sum 0.996. Answers identical across arms. Per the pre-registered rule: memory win real, CPU win not; SWITCH STAYS OFF. Next: ADR amendment with the measured table and this decision, then close the epic on the measurements.
Epic - awaiting ADR merge
- 08:43Z: amendment 1 (measured A/B + decision: switch stays off) pushed onto docs/adr-3565 as 6367baf; PR docs: add ADR-3565 bucketed final aggregate for fixed-width keys #3606 now carries ADR + amendment, guards clean. Final report posted (comment 6572833169). Remaining: owner merges docs: add ADR-3565 bucketed final aggregate for fixed-width keys #3606, then the epic closes; Bucketed final: charge the top-k transient, nullable-key and below-threshold top-k tests #3639 stays open.
Activity
pmoust commentedon Oct 31, 2026
Pre-registration: Stage 0 baseline with hardware counters (
pmu-20261031T2049Z). Written before launch. Nothing here has been run.What runs
Two boxes, both at main
000557f2f, each running the bench-box launcher'scpubuild (frame pointers on) with a newpmustage:pmuX-20261031T2049Z: c6a.4xlarge, the reference machine. Its timings are the baseline.pmuA-20261031T2049Z: c8g.4xlarge (Graviton4, Neoverse V2). Its counters are a fallback if the c6a hides the PMU. Its timings are never compared with the c6a's.Each box runs, in order:
benchmark.sh: the load, then the 43 statements x 3 tries.pmustage, on the set q02 (scan control), q09 q10 (low-cardinality keys with count distinct), q13 q14 q15 (SearchPhrase keys), q16-q19, q32-q35 and q36 (four derived Int64 keys). For each statement:perf stat -p, and reads schedstat CPU;SELECT COUNT(*) FROM (... GROUP BY ...).The candidate events are each probed on their own. The supported set goes to
pmu/events.txt.ls_dmnd_fills_from_sys.mem_io_local(demand fills from DRAM),ls_l1_d_tlb_miss.all.stall_backend_mem,ll_cache_miss_rd,dtlb_walk.Predictions
cyclescpu-clock. AWS exposes the x86 PMU only on full-socket sizes [inference].stall_backend_memandll_cache_miss_rdstall_backend_mem / cycles(c8g), q33 and q19find,insert_unique/insert_accountedandvectorized_appendframescreate_hashes(either call site)aggregate_batch_inner<FinalMarker>fullmain-20261031T1700Z[measured]; 16 B entries at load factor 0.5-0.875, 16 B of keys and 32 B of state, each doubled at most by growth [inference]fullmain) and the fetch cache [measured,fullmainstats]fullmain[measured]Validity checks
A failure makes a box's figures invalid, not a miss.
pmu/calls.tsv, each with a JSON answer whosestatusissuccess.benchmark.shanswers for the same statement, except q18 and q25, whose ties vary.benchmark.shhot times onpmuXfor q16-q19 and q32-q35 sit within 0.93-1.07 offullmain-20261031T1700Z(d29c8c5). The read-path diff from that commit to000557f2fis perf(logseg): build the page index per chunk on first lookup #3549 and the gate revert, neither in the aggregate path.What each outcome decides
dfbench-20261031T2032Z's component ratios (Epic: ClickBench hot geomean on the benchmark image: the per-statement floor, then the string and decode paths #3231 comment 6548777041), which is this epic's gate for building anything.pmoust commentedon Oct 31, 2026
pmuA-20261031T2049Zrecorded no counters: a launcher defect, not a box result. The event probe wrote each trial to one file without clearing it first, so a failed trial read the previous trial's output and every candidate event was accepted, including AMD event names on the Graviton.perf statandperf recordthen refused the list. The fix clears the file before each trial. Relaunched aspmuA2-20261031T2137Z(c8g.4xlarge, i-0c432c9319c651462) at the same commit000557f2f; the pre-registration (comment 6548953902) is unchanged, and P2 now reads against this box.pmuXis unaffected in principle (every x86 event is refused on c6a.4xlarge, so nothing stale could be accepted), and is still running.What
pmuAdid record, usable as registered:fullmain-20261031T1700Z, c6a). Band 90-150.pmoust commentedon Oct 31, 2026
Readout:
pmuX-20261031T2049Z(c6a.4xlarge, main000557f2f; pre-registration: comment 6548953902). INVALID for timing, on validity check 3. Memory and group figures below are observations, not registered results.Validity
success.benchmark.shanswers.fullmain-20261031T1700Z: q16 1.48, q17 1.36, q18 1.34, q19 1.22, q32 1.74, q33 1.03, q34 1.37, q35 1.34 (band 0.93-1.07). The whole 43-statement sweep is slow, median ratio 1.43, q02 included at 1.45; hot sum 48.69 s against 35.44 s. Server CPU-seconds are up with the wall (q34 53.8 CPU-s per call against 45.1 on prof028), so it is the host, not scheduling.fullmain(prof-lazy 1.036, prof-pre 1.062, prof028 1.053, median ratio over 43). The sampler records no CPU steal, so the cause stays unmeasured. No re-run: the timing baseline this epic needs isfullmain-20261031T1700Zplus the prof028 attribution, and the gate in the ADR is a same-box paired A/B.events.txtis empty and there are no counter or fill-profile files. Counters come frompmuA2.Observations (would have been P10-P13)
fullmain)Every memory figure sits inside its band on both boxes. q33's final table never hits: every (WatchID, ClientIP) pair is distinct, which fixes the first bucketed-final scope at the pure-insert case.
pmoust commentedon Oct 31, 2026
Readout:
pmuA2-20261031T2137Z(c8g.4xlarge, main000557f2f; pre-registration: comment 6548953902). Counters recorded this time. INVALID on validity check 4 and on the fill profile, so the ratios below are observations.perfscales the counts, so the ratios are physically plausible, but they are not the registered figures.ll_cache_miss_rd, the first supported name in the list, and on Graviton4 it reads 0 (so doesl3d_cache_refill). P7, P8 and P9 have no figure.pmuA3-20261031T2227Z(i-0f03ae6a7656081d1), same commit, with six events (cycles, instructions, stall_backend_mem, cache-misses, l2d_cache_refill, dtlb_walk) andcache-missesas the fill event. The bands are unchanged.Observations (median of 5 calls, scaled counts, server pid):
Against the registered predictions, as observations:
What this says so far: q33's whole-process cycles are 39% stalled on memory, with the highest DTLB walk rate of the set, and q36 (four derived Int64 keys over 9.8M groups) sits beside it. The scan-bound statements sit at 12-18%. Whether those stalls are the final's probe loop is what the fill profile answers;
pmuA3carries it.pmoust commentedon Nov 1, 2026
Readout:
pmuA3-20261031T2227Z(c8g.4xlarge, main000557f2f; pre-registration: comment 6548953902). VALID. This is the registered counter readout; P3-P9 read against it. Results:s3://ravel-clickbench-5b7101/reports/cold-floor/pmuA3-20261031T2227Z/sampler/pmu/(qNN.rN.stat.csv,qNN.fill.folded,calls.tsv,events.txt).Validity
success.benchmark.shtry-3 answers ondata.pmuX(c6a) was INVALID for timing (comment 6549550573), so Stage 0 has no fresh c6a timing box; the timing baseline staysfullmain-20261031T1700Zand the gate stays a paired same-box A/B.cache-misses,-c 2000, one call per statement. q02's fill profile is empty (0.19 s call;perf record -pattached after the call ended), so q02 has counters but no fill attribution.Counters (median of 5 calls, server pid)
Wall and CPU are c8g figures, for the record only; VmHWM matches
pmuA(q33 17.4 GB).Fill attribution (share of each statement's
cache-missessamples whose stack contains the frame; the first two columns overlap)FinalMarker)find/insert,match_tag,fix_insert_index, group-index push)RepartitionExec, not the finalcreate_hashesGroupValuesPrimitive::intern,find_or_find_insert_index)memcmpof the stored view)The repartition column is the 32-way hash repartition's scatter:
take_nativegathers (q33 0.196) and the per-partitionVec<i64>/Vec<i128>writes of the partial output. Leaf frames under the final for q33:match_tag(control-byte compare) 0.134, the group-indexpush<usize>0.095,find_inner0.045,fix_insert_index0.031; the key columns themselves do not appear, which is the pure-insert case (no hits, so no key compare).Against the bands
create_hashesmemcmp/memcpyunderFinalMarkerWhat this decides (per the pre-registration's outcome rules)
create_hashes0.08 on q34. The ADR's scope of fixed-width keys only stands, and nothing in this readout argues for bucketing string keys.dtlb_walkper kI (q33 1.14, 2.9x q02) instead. Per-bucket tables small enough for the L2 TLB are the mechanism the walk rate points at.pmoust commentedon Nov 1, 2026
ADR-3565 draft: adversarial review before the gate. Two rounds. Round 1 BLOCK, round 2 PASS with band-basis items, all applied. The design did not change; the gates did.
Blocks, both verified in code:
peak_intermediate_bytes, which charges the stock final's consumers at the reserve-ahead factor (1.25,crates/ravel-sql/src/memory.rs:1312-1318) and the new consumer at 1.0, so B/A reads 0.80 with no change in bytes. VmHWM is not a substitute: onpmuX-20261031T2049Z(pmu/calls.tsv) RSS before each q33 call is already 14.8-15.8 GB because the allocator keeps its arenas, and the per-call VmHWM delta reads 0.0-0.96 GB across five calls. The ADR now has PR1 add a factor-freepeakAskedIntermediateBytesstat and gates on that (B/A 0.45-0.75, miss > 0.85); the ledger and VmHWM figures are reported, not judged.preserve_partitioningsort, which Ravel never emits (repartition_sorts(false),session.rs:620): the top-k sits above a fetch-lessCoalescePartitionsExec. The ADR now reusesTopKPrefilter::above(compact_slices.rs:329-345) as written. The exactness argument is unchanged.Should-fix, applied:
COUNT(DISTINCT)besideAVGkeeps theListstate); q05 and q09 qualify throughSingleDistinctToGroupBy, pinned by the applicability test. The qualifying hot-sum was 8.55 s with q10 in it; it is 7.60 s (q05 q09 q16 q31 q32 q33 q36, min of tries 2 and 3,fullmain-20261031T1700Z). The non-addressed string cohort is 10.4 s, not 9.4 s.internhashes with the private seed); it takes back the router copy and runs only if decision 1 passes.SearchPhrase); probe-plus-scatter range for q33/q16/q36 is 0.72-0.76.size()undercount named and bounded atengage_groups; rule placed afterBoundedTopKAggregate; emitted batches own their buffers; bucket buffers are fixed 8192-row chunks so slack is bounded; boolean, Decimal128 and Duration keys refused and pinned.The band row for q33 CPU in the
pmuA3readout above (0.72-0.88) is superseded by the ADR's 0.82-0.90.pmoust commentedon Nov 1, 2026
dispatch-intent nonce=1793553159-50607 ticket=3607 ref=eccc364f0c4f222bd47543651272c066ce63e4ef
pmoust commentedon Nov 1, 2026
dispatch-record nonce=1793553159-50607 task=fb395810-0d79-4dc3-8149-5bc12db19c31
pmoust commentedon Nov 1, 2026
dispatch-intent nonce=1793553423-72738 ticket=3607 ref=f1c0d169d18a88dd2f2d38bdba059077e0ac5996
pmoust commentedon Nov 1, 2026
dispatch-record nonce=1793553423-72738 task=cf871352-c4b6-4589-b98f-f2542ea8a6d3
docs: correct the citations in ADR-3565 after review
docs: make the ADR-3565 shares and bounds precise
docs: separate the exchange's share from its scatter in ADR-3565
pmoust commentedon Nov 1, 2026
dispatch-intent nonce=1793561187-50794 ticket=3607 ref=04b24a5aaf5413723da69bf94a779d7aecda3a20
4 remaining items
pmoust commentedon Nov 1, 2026
dispatch-record nonce=1793571288-52459 task=4d44de9b-d28a-4f0b-8b44-116c5854e19c
pmoust commentedon Nov 2, 2026
dispatch-intent nonce=1793588401-95592 ticket=3609 ref=0d5733c26a3a1817cf0771d267bfd04e0d07c46f
pmoust commentedon Nov 2, 2026
dispatch-record nonce=1793588401-95592 task=c33514e9-c01c-4561-9a93-e2f0552d574d
pmoust commentedon Nov 2, 2026
Pre-registration: ADR-3565 step 3, paired A/B of the bucketed final (
bfab-20261102T0716Z). Written before launch. Nothing here has been run. Bands are copied from ADR-3565 decision 6 (docs/adrs/3565-bucketed-final-aggregate.md on PR #3606 head 26ecaf5); the asked-bytes row reads thepeakAskedIntermediateBytesstat that #3607 added.What runs
One c6a.4xlarge (the reference machine), bench-box launcher stage
cpuab, A and B the SAME commit, main 6df1524 (after #3640, carries #3630 serve-if-fits), recorded asravel-commit-A.txt=ravel-commit-B.txt), B started with--sql-bucketed-final-aggregationthroughRAVEL_B_FLAGS(recorded incpuab/arm-B-flags.txt). Eight server lifetimes in the order A B B A B A A B; per lifetime every statement once warm then 3 timed calls (10 for q01, q02, q07, q43). Per call the launcher records wall, server CPU-ns, server ticks, RustFS CPU-ns and VmHWM after a per-callclear_refsreset, and keeps every call's JSON answer with itsstats.accountingblock (peakIntermediateBytes,peakAskedIntermediateBytes).Figures per statement and arm are medians over the 12 timed calls (4 lifetimes x 3), B/A is the ratio of medians. Hot wall is the median too.
Predictions
pmuA3, #3565 comment 6560647859)peakAskedIntermediateBytes, B/ApeakIntermediateBytes, B/ApmuXcalls.tsv, measured 2026-10-31)the_plan_is_unchanged_when_the_field_is_noneand the refusal tests)--sql-max-query-bytestopk=in EXPLAINCoalescePartitionsExec; checked once per statement from an EXPLAIN pass before the timed roundsValidity checks
A failure makes the run invalid, not a miss.
cpuab/calls.tsv, every answer JSON hasstatus: success.ravel-commit-A.txtequalsravel-commit-B.txt;arm-B-flags.txtis exactly--sql-bucketed-final-aggregation;binaries.sha256shows A and B differ only by the wrapper.fullmain-20261031T1700Z(d29c8c5) and the ratios are reported. This is no longer a hard check: main since 6b85f6c (feat(logseg): serve LIKE-deferred columns when the scan fits the cache #3630, serve-if-fits) changes what the 8 GB decoded cache holds in a cpuab process (q21 and q24 fill it with deferred columns), so single-arm figures from before that commit are not a clean basis on cached statements. Both arms carry it, so every B/A ratio above is unaffected. A ratio outside 0.80-1.25 on the scan control q02 still marks the box unstable (check 3 covers the within-run spread).peakAskedIntermediateBytesis present and nonzero on every q33 answer in both arms.What each outcome decides
routed_chunk_rows1024 through a config amendment before judging.pmoust commentedon Nov 2, 2026
Readout:
bfab-20261102T0716Z(c6a.4xlarge, i-0da38ead1503067fb, stagecpuab; pre-registration: comment 6571398126). A = B = main6df152450; arm B started with--sql-bucketed-final-aggregation(cpuab/arm-B-flags.txt); eight lifetimes A B B A B A A B, 1,256 timed calls. Results:s3://ravel-clickbench-5b7101/reports/cold-floor/bfab-20261102T0716Z/sampler/cpuab/(calls.tsv,qNN.<arm><round>.r<rep>.json). Readout script:readout_bfab.pyin the epic artifacts (medians over the 12 timed calls per arm, B/A of medians).Validity
status: success.ravel-commit-A.txt=ravel-commit-B.txt= 6df1524; B's wrapper is exactly the flag;binaries.sha256differs only by the wrapper.fullmain-20261031T1700Z: q33 1.05, q19 1.22, q17/q18 1.36-1.37, q34/q35 1.39, q16 1.51, q02 1.75, q32 1.76. The box runs every statement slower than the baseline, scans most, thepmuXshape; both arms share it, and the within-run spread is under 1%, so the B/A ratios stand and single-arm figures do not.peakAskedIntermediateBytespresent and nonzero on every q33 call in both arms.Figures (medians; CPU is server CPU-seconds per call, asked is
peakAskedIntermediateBytes, charged ispeakIntermediateBytes, VmHWM reset per call)Against the bands
topk=on the exec lineWhat this says
SearchEngineID, ClientIP, 32 partitions, LIMIT 10) regresses 3% in CPU and 1.9x in asked bytes.routed_chunk_rowsdefault (the memory regression on small statements), and decision 5's fused routing (the only lever left on q33's CPU, since the router copy is what the bucketed final adds and the exchange's 0.36 share of misses is untouched). Neither is measured here.pmoust commentedon Nov 2, 2026
Correction to the readout above (comment 6572771621), validity check 4. The arm A to
fullmain-20261031T1700Zratios (1.05 on q33 up to 1.75 on q02 and q32) are a protocol difference, not thepmuXslow-box shape:fullmainis the restart protocol (one statement per server process, min of tries 2 and 3), while acpuabbox runs all 43 statements in one process with a full 8 GB decoded cache. The #3231 session ran the restart protocol on main 0d5733c the same day (fullserveA-20261102T0316Z, c6a.4xlarge) and got a hot geomean of 1.014 againstfullmain, q02 1.02, cold sum 105.78 s against 105.73 s. So single-arm cpuab figures are compared only with cpuab figures; every B/A ratio in the readout is unaffected, and the "slower box" sentence is withdrawn.docs: add amendment 1 to ADR-3565 with the measured A/B and the decision
pmoust commentedon Nov 2, 2026
Final report: cache-aware aggregation and memory-local query execution
Recommendation: reject enabling by default; keep the measured implementation behind its switch. The bucketed final aggregate halves q33's peak memory at exactly equal CPU and wall. The memory-latency thesis for q33's time does not survive the end-to-end A/B: the locality gained in the final's probe is spent again on the extra row copy, hash and chunk writes the bucketing adds.
What was measured (Stage 0)
fullmain-20261031T1700Z(main d29c8c5, c6a.4xlarge), hot sum 35.44 s; cohort q16-q19 and q32-q35 is 15.1 s; q33 peaks at 11.8 GB charged, 99,997,493 groups over as many rows (every (WatchID, ClientIP) pair unique).pmuA3-20261031T2227Z, c8g.4xlarge; the c6a exposes no PMU): q33's server cycles are 35.5% memory-stalled (scan control q02: 11.5%), 1.14 DTLB walks per 1k instructions (q02: 0.40). Cache-miss samples: 0.40 in the final's group-table probe, 0.36 in the hash repartition's scatter, 0.19 in decode, 0.007 in hashing. String-keyed statements are decode-led, which fixed the scope at fixed-width keys. Comments 6560647859 (counters), 6549550573 and 6549461554 (memory, groups).What was built (Stages 1-5, all merged, all off by default)
peakAskedIntermediateBytes, a factor-free memory stat, because the ledger figure charges stock aggregates at 1.25 and VmHWM is a process lifetime figure. Adversarial checkpoint found the gauge measures live reservations, not charged/1.25; docs and tests were corrected to that meaning.BucketedFinalAggregateExecbehindSqlConfig::bucketed_final_aggregationand--sql-bucketed-final-aggregation. Differential tests at 1/4/32 partitions, refusal tests, memory refusal and cancellation tests, reachability through the server entry. Checkpoint pass; found the chunk-slack default is about 9x the ADR's estimate.The A/B (step 3,
bfab-20261102T0716Z, pre-registered as comment 6571398126, readout 6572771621)Paired, same box, same binary, eight alternating lifetimes, A/A spread 0.9%.
Where the thesis held and where it did not
unsafe).What a reader should do with this
--sql-bucketed-final-aggregation; it is measured, reviewed and tested, and off by default.pmuand arm-Bcpuabstages are preserved in the epic's artifacts and S3 prefixes named in the ledger.Remaining for the owner: merge PR #3606 (ADR plus amendment 1), then this epic closes; #3639 stays open as the follow-up ticket.
pmoust commentedon Nov 2, 2026
Closing. ADR-3565 with amendment 1 landed on main in 76f5472 (PR #3606). Every stage is done: Stage 0 counters and baseline, the ADR, the asked-bytes stat (#3607), the bucketed final aggregate behind its switch (#3608), exact per-bucket top-k (#3609), and the paired A/B (readout comment 6572771621, final report comment 6572833169). Outcome per the pre-registration: the memory band passed (q33 peak asked bytes 0.623), the CPU and wall bands missed (1.000, 1.004), so the switch stays off by default and no unproven optimisation is enabled. #3639 holds the follow-ups.
docs: add ADR-3565 bucketed final aggregate for fixed-width keys
docs: correct the citations in ADR-3565 after review
docs: make the ADR-3565 shares and bounds precise
docs: separate the exchange's share from its scatter in ADR-3565
docs: add amendment 1 to ADR-3565 with the measured A/B and the decision