Repository navigation
stage_pipeline: write the memo after each step, the selected choice and the enumeration #1003
Description
Problem, by example
Example workload: one query, evaluated every minute, with max_error 0.01 and failure_probability 0.001:
avg by (job) (quantile_over_time(0.5, data[5m]) / quantile_over_time(0.5, data[5m]) + quantile_over_time(0.9, data[5m]))
The memo the planner builds for it on main (0f7465c96), measured with LogicalMemo::from_roots, try_populate_pass1 and the expanded_memo test helper:
| Group | Logical sub-expression | Reads |
|---|---|---|
| G0 | Scan data |
– |
| G1 | TimeRange 5m |
G0 |
| G2 | per-series quantile 0.5 |
G1 |
| G3 | per-series quantile 0.5 (structurally equal to G2, but a separate group; see below) |
G1 |
| G4 | G2 / G3 |
G2, G3 |
| G5 | per-series quantile 0.9 |
G1 |
| G6 | G4 + G5 |
G4, G5 |
| G7 | avg by (job) |
G6 |
| After | Groups | Implementation candidates | Full candidate DAGs |
|---|---|---|---|
| Stage 0 | 8 | – | 1 |
| Stage 1, Pass 1 | 8 | 26 (G2, G3 and G5 have 7 each) | 343 = 7 × 7 × 7 |
| Pass 2 window composition + concrete state groups | 378 (360 are PaneChain shared-build groups) |
2614 (G2, G3 and G5 have 745 each: 13 Pass 1, 360 Panes, 372 SharedBuild) |
> 1,000,000 (a naive enumerator stopped there) |
G2 and G3 are separate groups because share_common_sub_dags (crates/types/src/ir/cse.rs) shares a non-ASAP node only when its schema has a provable unique key, and this per-series quantile's output schema has none.
stage_pipeline --workload wl.json --out doc.json on this workload writes an asap-stage-pipeline/v1 document with the Stage 0 DAG, the selected plan's Stage 1 and Stage 2 DAGs, and the search report. None of the tables above can be read from it:
- no groups;
- no candidates per group, and no rule (
CandidateOrigin) that added each one; - no record of which candidate the MILP chose for each group.
So neither the DAG viewer nor the CI snapshots can show what each stage did to the memo.
Why
stage_pipeline (crates/devtools/src/bin/stage_pipeline.rs) serializes the stage results. It never serializes LogicalMemo or CandidateMemo (crates/logical-asap-optimization/src/memo.rs, memo/candidates.rs), and the selected plan's report holds its candidate connections, not a group → candidate map.
Proposal
Add an optional memo section to the document:
snapshots: one per step:stage0(after hash-consing),pass1,pass2andconcrete_states(the last runs in Stage 3 today; the Stage 1 reorganization moves it).- Each snapshot lists its groups. A group has
id, its operator label,children,parents,queriesandroots_of. - Each group lists its candidates. A candidate has
id,implements,children,origin,budget_share, and its own nodes in the document's existing node format.
- Each snapshot lists its groups. A group has
selected_choice: the selected plan's group → candidate map.enumeration:count,completeand the first--max-enumeratedchoices (default 200), frommemo::enumerate.
The section is on by default. The CI snapshots keep enumeration.count and complete but drop the choice list.
Scope and acceptance
- A
stage_pipelinetest on the example above:stage0has 8 groups,pass1has 26 candidates, and thepass1enumeration is 343 withcomplete: true. - Every candidate in
selected_choiceexists in the last snapshot. - The viewer's document validator (
tools/dag-viewer/stages.js) accepts documents with and withoutmemo.
Depends on #1002 (memo → candidate DAGs).
Activity
feat(dag-viewer): show an enumerated choice that does not compose
feat(dag-viewer): show an enumerated choice that does not compose
feat(dag-viewer): list the enumerated candidate DAGs and draw any of …
zzylol commentedon Oct 28, 2026
Corrected 2026-10-28: production numbers from the stage-pipeline document; earlier numbers came from a test helper without the series-identity column.
The example in this issue was measured with
asap_integration_tests::fixtures::lower_promql/expanded_memo. That lowering omits the PromQL series-identity column, so the per-series quantile had no unique key and the twoquantile_over_time(0.5, data[5m])stayed two groups (G2, G3). The production frontend adds$promql_series_identity, which gives the per-series output a unique key (series identity, timestamp), so they share one group. Production numbers, fromtools/dag-viewer/examples/memo-ratio-of-quantiles.json(mainatd728d05c):Scan dataTimeRange 5mquantile 0.5(both occurrences)G2 / G2quantile 0.9G3 + G4avg by (job)budget_share1)Panes)PaneChainshared-build groups)Panes, 345SharedBuild)For this issue: the
stage_pipelinememo test (merged in #1024) already checks the production numbers (7 groups, 19 candidates, 49 complete Pass 1 choices), not the 8 / 26 / 343 above.