Skip to content

stage_pipeline: write the memo after each step, the selected choice and the enumeration #1003

Closed

Description

@zzylol

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, pass2 and concrete_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, queries and roots_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.
  • selected_choice: the selected plan's group → candidate map.
  • enumeration: count, complete and the first --max-enumerated choices (default 200), from memo::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_pipeline test on the example above: stage0 has 8 groups, pass1 has 26 candidates, and the pass1 enumeration is 343 with complete: true.
  • Every candidate in selected_choice exists in the last snapshot.
  • The viewer's document validator (tools/dag-viewer/stages.js) accepts documents with and without memo.

Depends on #1002 (memo → candidate DAGs).

Activity

  1. added
    enhancementNew feature or request
    dag-viewerDAG export and visualization tooling
    dev-toolsDeveloper tools, diagnostics, and demos
    on Oct 28, 2026
  2. zzylol commented on Oct 28, 2026

    @zzylol
    ContributorAuthor

    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 two quantile_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, from tools/dag-viewer/examples/memo-ratio-of-quantiles.json (main at d728d05c):

    Group Logical sub-expression Reads
    G0 Scan data –
    G1 TimeRange 5m G0
    G2 per-series quantile 0.5 (both occurrences) G1
    G3 G2 / G2 G2 (both operands)
    G4 per-series quantile 0.9 G1
    G5 G3 + G4 G3, G4
    G6 avg by (job) G5
    After Groups Implementation candidates Candidate DAGs
    Stage 0 7 – 1
    Stage 1, Pass 1 7 19 (G2 and G4 have 7 each) 49 = 7 × 7 (16 use only budget_share 1)
    Pass 2 window composition 7 259 (G2 and G4 have 127 each: 7 Pass 1, 120 Panes) > 1,000 (enumeration limit)
    Pass 2 rules + concrete state groups 354 (336 are PaneChain shared-build groups) 1738 (G2 and G4 have 691 each: 10 Pass 1, 336 Panes, 345 SharedBuild) > 1,000 (enumeration limit)

    For this issue: the stage_pipeline memo test (merged in #1024) already checks the production numbers (7 groups, 19 candidates, 49 complete Pass 1 choices), not the 8 / 26 / 343 above.

  3. added a commit that references this issue on Nov 6, 2026
    d728d05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    dag-viewerDAG export and visualization toolingdev-toolsDeveloper tools, diagnostics, and demosenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions