Skip to content

Stage 1 Pass 1 review: per-computation candidates over memo groups #1012

Open
1 of 26 issues completed
Open

Description

@zzylol

Part of #1010. Line numbers are on main at 958778400. Abbreviations: loa/ = crates/logical-asap-optimization/src/, pcc.rs = loa/per_computation_candidates.rs.

Example: what Pass 1 produces

Same query as the tracking issue (avg by (job) (quantile_over_time(0.5, data[5m]) / quantile_over_time(0.5, data[5m]) + quantile_over_time(0.9, data[5m])), every minute, max_error 0.01, failure_probability 0.001). In the production planner, read from the stage-pipeline document tools/dag-viewer/examples/memo-ratio-of-quantiles.json (main at d728d05c) (#1002):

Corrected 2026-10-28: production numbers from the stage-pipeline document; earlier numbers came from a test helper without the series-identity column. The whole-workload column is the earlier helper measurement and has not been re-measured in production.

Memo path (try_populate_pass1) Whole-workload path (compose_logical_candidate over every inventory assignment)
Memo groups 7 (same as Stage 0) –
Implementation candidates 19: G2 and G4 have 7 each, the other 5 groups 1 each –
Candidate DAGs 49 = 7 × 7; 16 use only budget_share 1 27 (helper)

Where the 7 candidates of a quantile group come from (read from the code, not from a run):

  • computation_candidates_for_intent (pcc.rs:495) gives Quantile no exact accumulator, so its choices are Exact, Summary(Kll), Summary(DDSketch) (families from summary_candidates, loa/realization.rs:30).
  • pass1_candidates (loa/memo/groups.rs:91) counts, for each query, the targets it reaches that offer a summary (here 2: G2, G4), and takes the maximum over the group's queries as max_budget_share (groups.rs:118-151).
  • target_candidates emits one candidate per budget share 1..=max_budget_share for every sketch choice (groups.rs:204-215); the exact choice stops at share 1. In production the 7 are: exact; KLL k 345 and DDSketch alpha 0.01 for the whole budget; KLL k 746 and DDSketch alpha 0.004963 for half of it; and KLL k 567 (whole budget) and k 1184 (half) sized for the whole result over the source's 10,000 series (per-value failure_probability 1e-7 and 5e-8).

Open question (#1002): why the memo stands for more DAGs than the whole-workload path composes (production: 49, of which 16 whole-budget; helper: 343 vs 27). One hypothesis from reading the code: the whole-workload path splits the budget by the sketch states actually present in a composed candidate (budget_shares, pcc.rs:1302), while the memo offers every share up to the maximum, so the memo includes over-split variants that the whole-workload path never builds. Not confirmed; #1002 adds the memo-to-candidate-DAG enumeration that can check it.

Functionality

  • Find each computation target (an Aggregate whose intent has an exact accumulator or a summary family), including fused computations (whole-expression top-k, nested sum).
  • For each target, list its candidates on its own: exact, exact accumulator state, each summary family that can meet its accuracy requirement. Prune only what provably cannot, with a reason.
  • Turn the targets' candidates into each memo group's ImplementationCandidates (CandidateMemo).

Concepts (design doc term ↔ code)

planner-layering.md Code on main
eligible sub-DAG / target ComputationTarget (pcc.rs:259)
fused computation FusedComputation (pcc.rs:290)
Replacements(s) ComputationCandidate { Exact, ExactAccumulator(Vec<ExactKind>), Summary(SketchAlgorithm) } (pcc.rs:141)
Pass 1 inventory PerComputationCandidates { roots, targets } (pcc.rs:343)
PRUNE(c, reason) PruneReason (pcc.rs:171)
summary-family capabilities F FamilyCapability (types/src/ir/schema/family_capability.rs), but Pass 1 reads the hand-written summary_candidates
rewrite rule (pattern + templates), Algorithm 1.1 no rule type; per-intent code in pcc.rs
implementation candidate ImplementationCandidate { implements, node, children, origin, budget_share } (loa/memo/candidates.rs:24), origin = CandidateOrigin::Pass1
Memo after Pass 1 CandidateMemo (candidates.rs:15)

Interfaces: designed vs implemented

Designed Implemented
Input logical roots; the deployment's supported summaries generate_per_computation_candidates_with_memo(Vec<(Id, QueryRoot)>, &LogicalMemo) (pcc.rs:608), called from Stage 3's driver (plan-selection/src/search/pipeline.rs)
Deployment filter part of Pass 1 PerComputationCandidates::without_unsupported_summaries (pcc.rs:413), called by plan-selection (pipeline.rs:214), with DeploymentCapabilities defined in this crate (loa/deployment_capabilities.rs:48)
Output CandidateMemo: each group's candidates try_populate_pass1(LogicalMemo, &PerComputationCandidates) -> Result<CandidateMemo, _> (groups.rs:48), called from Stage 3 (plan-selection/src/search/route.rs, search_domain)
Sizing "Summary nodes are created unsized" (doc); pcc.rs:22 says the same memo Pass 1 composes and sizes each candidate per budget share (groups.rs:204-215 → compose_alone → sized)

Interface decided in #1047 (2026-10-29)

Decided in #1047, the stage-boundary types. Its sub-issue is #1048. Names follow #1050 within #1047's scope. Principles: a stage or pass reads only the previous step's output type and calls no other stage; the asap-planner facade runs the stages in order; each stage prunes only what its own knowledge rules out and records it as PrunedCandidate<C> { group, candidate, reason: PruneReason }; queries are entry indices. Planning stays as fast as on main: P6 and P7 report before/after stage_pipeline runs.

Decided
Functionality For every aggregate measure with a realization besides plain exact: list how it can be computed, turn each way into sized CandidateForMemoGroups on its group, and prune
Input DagOfLogicalMemoGroups (moved in), accuracy targets (one per query), AccuracyModel, AccuracyEvidenceProvider, DataWorkload, supported summaries (the Stage 1 part of DeploymentCapabilities)
Output PerComputationLogicalMemoCandidates { memo (private: groups + candidates_by_group), query_roots, computation_targets, pruned_candidates: Vec<PrunedCandidate<HowToComputeAggregateMeasure>> }. This is also the Pass 2 input
Function generate_per_computation_logical_memo_candidates(...); one entry replacing generate_per_computation_candidates{,_with_memo}, with try_populate_pass1, add_scoped_candidates and without_unsupported_summaries inside Pass 1
ComputationTarget aggregate_group, measure_index, ways_to_compute: Vec<HowToComputeAggregateMeasure>, measure_filter, having_filters, fused_computation (absorbed_targets, ways_to_compute); raw_data_samples_of leaves the output
Prunes summaries the deployment does not support; candidates provably unable to run or to meet the requirement; new: candidates whose composed guarantee cannot meet the query's accuracy target in any combination within that query (moved from prune_query_candidates)
Decided here Pass 1 walks memo groups (an Aggregate group is a target) instead of whole roots. #1047 P3 implements it, and reviewing P3 settles this question. Whether computation_targets can be deleted is open here; the evidence is in the comment above
PR #1047 P3 (and P6 for the accuracy pruning)

Review checklist

User's suspected problems

  • (a) The intent → summary family table is duplicated. Places that encode intent ↔ family / exact state:
    • summary_candidates (loa/realization.rs:30): intent → families, written by hand; its comment says "one home". Theta is excluded by hand.
    • of_per_instance_sketch (types/src/ir/schema/family_capability.rs:150): family → readouts. This is the table the executor reads (executor/src/supported_summaries.rs). summary_candidates could be derived from it ([Refactor] Derive Pass 1's intent → family list (summary_candidates) from the family-capability table #953).
    • statistic (pcc.rs:3875): intent → SketchStatistic, used to check deployment support (pcc.rs:418); the executor maps readout → SketchStatistic again (executor/src/supported_summaries.rs:119).
    • computation_candidates_for_intent (pcc.rs:495) and loa/function_rules.rs:10: the same intent set (Sum, Min, Max, Avg, Rate, IRate, Increase) for exact accumulators and for accuracy composition.
    • sizing_target (realization.rs:72) and intent_metric (types/src/ir/properties/accuracy.rs:319): two intent → accuracy metric tables.
    • keeps_every_value (plan-selection/src/lib.rs:4963): intents whose exact state grows, in Stage 3.
    • agg_is_mergeable, agg_is_exact, agg_accuracy (types/src/ir/operator/agg_intent.rs:670, :682): no non-test caller found; agg_is_exact says Count has no sketch benefit, which summary_candidates contradicts.
  • (b) Stages mixed in Pass 1. Only about the first 1,200 lines of pcc.rs (6,154 lines) generate candidates. The rest is whole-workload composition (compose_logical_candidate, pcc.rs:1203), sizing (budget_shares, sized), guarantee propagation including Pass 2 / Stage 2 shapes (PaneChain, rollup, merged panes), and substitution. Stage 3 imports guarantee functions from it (bind_guarantees, with_resized_summaries, root_guarantee, …).
  • (b) Stage 3 drives Pass 1. try_populate_pass1 and accuracy_configurations::add_scoped_candidates (a plan-selection module that adds Stage 1 candidates, plan-selection/src/search/candidates/accuracy_configurations.rs:8) run inside Stage 3's search_domain (route.rs). Unsupported summaries are removed by the Stage 3 driver, not by Pass 1 (Reorganize by stage: one memo-based Pass 2, Stage 1 finishes the memo, deployment inputs split by stage, asap-planner as the only entry point #958 decisions 2–3). [Refactor] One named type per stage boundary: PerComputationLogicalMemoCandidates → LogicalASAPMemoCandidates → PhysicalASAPMemoCandidates #1047 P3: try_populate_pass1, add_scoped_candidates and without_unsupported_summaries move inside Pass 1; Stage 3 reads only Stage 2's output.
  • (c) Not general. There is no pattern/template rule as in Algorithm 1.1. Families come from a fixed list; per-intent prunes are inline (nullable avg, relational count, quantile φ, top-k heap over one row per item); fused computations are bespoke passes (add_whole_expression_targets, add_nested_sum_targets); substitution has per-intent builders (ranked_as_topk, nested_sum, sql_avg). Adding an intent or family means editing several tables ([Refactor] Derive Pass 1's intent → family list (summary_candidates) from the family-capability table #953, Design the extension contract for new ASAP primitives and sketch algorithms #548).

1. Boundaries

2. Naming and over-abstraction

3. Stale code

4. "memo" / "cache"

5. Rules over memo groups

Correctness and efficiency

Input/output check with the DAG viewer

Related issues

Sub-issues: #953, #735, #727, #703, #681, #664, #639, #750, #751, #743, #739, #711, #689, #798, #807, #788, #944, #459, #410, #422, #444, #443, #350, #172, #297.

Referenced: #1002 (memo → candidate DAGs; parent #1008), #958 (Stage 1 layout, under #1013), #929 (size once, under #1015), #548 and #131 (extension contract).

🤖 Generated with Claude Code

Activity

  1. added
    accuracyAccuracy requirements, guarantees, and propagation
    optimizerWorkload optimization and plan selection
    on Oct 28, 2026
  2. 3 remaining items

  3. zzylol commented on Oct 29, 2026

    @zzylol
    ContributorAuthor

    Evidence for removing computation_targets from the Pass 1 output (#1047, decided 2026-10-29): after Pass 1 builds the implementation candidates, most of ComputationTarget is duplicated there. measure_filter is SummaryAgg.filter, having_filters are Filter nodes above the aggregate, candidates are the sized implementation candidates, and FusedComputation.absorbed_targets is ImplementationCandidate.fused_groups. Readers left after #1047: devtools display and combinations() (plan-selection's weight check reads raw_data_samples_of, which #1047 already removes from the output). Decide here whether computation_targets can be deleted.

  4. zzylol commented on Oct 29, 2026

    @zzylol
    ContributorAuthor

    From #1047 (decided 2026-10-29): LogicalMemo no longer exposes the node-pointer → group map (group_of). Its public shape is { groups, query_roots: Vec<QueryRoot<MemoGroupId>> }. Pass 1 should find its targets by walking memo groups (an Aggregate group is a target) instead of walking the frontends' nodes and mapping them back to groups (group_for_node in per_computation_candidates.rs:710 and groups.rs). This issue decides how Pass 1 does that.

  5. zzylol commented on Oct 30, 2026

    @zzylol
    ContributorAuthor

    Pass 1 correctness bug found while working on #1047 P3c: #1089. A key-only HAVING is pushed below an aggregate that another query shares unfiltered.

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

    accuracyAccuracy requirements, guarantees, and propagationoptimizerWorkload optimization and plan selectionrefactor

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions