Skip to content

feat(planner): Stage 2 physical candidates and Stage 3 cost-based selection - #576

Draft
zzylol wants to merge 2 commits into
stack/509-c2-stage1-workloadfrom
stack/509-c3-stage2-3
Draft

zzylol wants to merge 2 commits into
stack/509-c2-stage1-workloadfrom
stack/509-c3-stage2-3

Conversation

@zzylol

@zzylol zzylol commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #575 (Stage 1 whole-workload candidates), which sits on #561 and #543. Next: the Example 1 acceptance tests (#577).

Why

#509 Stage 2 turns each logical candidate into a physical one, and Stage 3 is the only stage that uses cost to pick one plan. Before this PR the pipeline stopped at Stage 1, so it selected no plan.

What

  • physical_candidates::stage2_physical(from_logical, roots) (Stage 2 MVP): one physical candidate for each logical candidate.
    • Exact Aggregate{[TopK]} becomes a per-group Sort followed by a per-group Limit, the shape the PromQL frontend uses for generic topk.
    • A summary stays build → estimate.
    • Timing comes from refactor(ir): remove legacy scaffolding and finish tooling migration #543's default MaterializationAssignment, so every summary state is computed at query time. A MaintainPopulation is rejected because it runs only at ingestion time.
    • The timed roots are exported as one PhysicalASAPDAG, and a node shared by two roots stays one node.
  • plan_selection (Stage 3): rejects a candidate whose summary misses its query's accuracy target, or that uses Count-Min over weights not proven non-negative. It prices every valid candidate node by node with analytical_cost (illustrative statistics), selects the cheapest, and reports every other candidate as costlier.
  • stage_pipeline now also emits stage2_physical_asap and stage3_selection. The fixture is regenerated.
  • Unit tests:
    • Stage 2: exact top-k becomes sort then limit, and a shared child stays shared. With summaries, no node runs at ingestion time.
    • Stage 3: an accuracy failure and Count-Min over signed weights are rejected as invalid, and the per-node costs cover the DAG and sum to the total.

How

Stage 2 rewrites bottom-up with one memo for the whole workload, so sharing between roots is preserved. It then applies apply_materialization_timings with MaterializationAssignment::default(); it chooses no materialization. Stage 3 charges each DAG node once, so a shared node costs the same whatever the number of consumers.

Example 1

Before this PR: stage_pipeline stops at 24 Stage 1 candidates, and no plan is chosen.

After this PR: the counts are 1 → 24 → 24 → 1.

  • Stage 2: 24 physical candidates, all at query time. Exact Q2 is sort (partition by job) → limit 10.
  • Stage 3 selects P20, Q1 exact (Sum acc, Rate acc) · Q2 exact (Sum acc), at 79.4 cpu_ms per workload evaluation.
  • The 8 CMS+heap candidates are invalid: their update weights are not proven non-negative.
  • The other 15 are valid but costlier: exact variants cost 80.4–86.4 and CountSketch+heap variants 191.4–198.4.

Known gaps

  • Stage 1 gives 24 candidates, not the doc's 6, because of the exact-accumulator options. There is no Hydra and no shared-input variant.
  • Only 8 of 24 physical candidates compile in the physical planner, all with exact Q2:
    • CountSketch+heap: the schema the summary produces does not match what the runtime builds.
    • CMS+heap: needs non-negative weights.
  • Costs use illustrative statistics. They show the mechanism, not real measurements.
  • There is no latency check, so Q2's 100 ms bound is not enforced.

The fixture tools/dag-viewer/examples/planner-layering-example1.json was regenerated after the rebase onto #543. It is byte-identical to the pre-rebase version.

🤖 Generated with Claude Code

zzylol and others added 2 commits October 3, 2026 21:40
…ction (MVP)

Stage 2 implements one logical candidate physically: exact Aggregate{[TopK]}
becomes Limit(Sort) per group, summaries stay build -> estimate, every node
runs at query time, and the timed roots export as one PhysicalASAPDAG.

Stage 3 is the only stage that computes cost. It rejects candidates whose
summary misses its query's accuracy target (accuracy model, analytical
guarantee) or uses Count-Min over weights not proven non-negative, prices
every valid candidate node by node with analytical_cost (illustrative
statistics), selects the cheapest and reports the rest as costlier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add stage2_physical_asap (no cost) and stage3_selection (costs, selected,
every other candidate rejected as invalid or costlier) per the v2 contract.
PromQL rows carry the series identity from Stage 0, as runtime state needs.
Regenerate tools/dag-viewer/examples/planner-layering-example1.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant