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.

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