Skip to content

planner-layering.md: the memo through the stages, and the memo to candidate DAGs algorithm #1007

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.

A reader of docs/design_docs/proposals/planner-layering.md can't predict these tables. The section "The stages over a memo of sub-expressions" says what each stage decides over groups, but it is out of date:

  • it names crates/plan-selection/src/search/memo/, brute_force::memo and brute_force::brute_force, which no longer exist;
  • it gives no worked example of a memo through the stages;
  • it doesn't say how the memo maps back to the vector of candidate DAGs the stage table describes.

Proposal

Rewrite that section. Prose the author wrote elsewhere stays unchanged, and the section cites no PR numbers. It covers:

  • Components:
    • LogicalMemo and MemoGroup (Stage 0 hash-consing, including when two equal nodes stay separate groups);
    • CandidateMemo, ImplementationCandidate and CandidateOrigin (which rule adds which candidates).
  • The example above through each step: groups, candidates and candidate-DAG counts per step, as the code produces them.
  • Algorithm, memo to candidate DAGs: MemoChoice, memo::enumerate and memo::compose, and why the count is not a product over groups (shared groups, fused candidates, groups needed only under some choices).
  • Code interface: current paths and entry points.

Scope and acceptance

Activity

  1. added
    documentationImprovements or additions to documentation
    dag-viewerDAG export and visualization tooling
    on Oct 28, 2026
  2. added a commit that references this issue on Oct 28, 2026
    d341424
  3. 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 design doc's memo example now uses these production numbers.

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 toolingdocumentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions