Skip to content

Stage 3 review: plan selection, the MILP over the memo, cost and accuracy constraints #1015

Open
4 of 32 issues completed

Description

@zzylol

Part of #1010. Line numbers are on main at 958778400. ps/ = crates/plan-selection/src/.

Example: what Stage 3 receives and returns

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). Stage 3 receives a memo of 354 groups and 1738 implementation candidates (#1008; corrected 2026-10-28: production numbers from the stage-pipeline document; earlier numbers came from a test helper without the series-identity column.) It must pick one candidate per needed group (and one producer per input), check accuracy and latency, price the plan, and return one plan.

How many candidate DAGs that memo stands for is not known: a naive enumerator stopped at 1,000,000 (#1002). So today nobody can check, even for this one query, that the selected plan is the cheapest the memo allows (#1004).

Functionality

  • Reject candidates that miss an accuracy target, a latency bound or a deployment capability; pick the cheapest valid plan for the whole workload under the deployment's cost model.
  • It is the only stage that uses the cost and accuracy models and the only one that discards valid candidates.

Concepts (design doc term ↔ code)

planner-layering.md Code on main
Resource usage ResourceUsage (ps/lib.rs:333)
Cost model 1/2/3 CostModel (ps/lib.rs:475) with ResourceUnitCost (:361), ClusterLimits (:492), ClusterObjective (:506)
Settings PlanSelectionSettings (ps/lib.rs:523)
Choose one candidate per group (memo search) candidate_connection_search (ps/search/route.rs:62) → search_domain (:653) → MILP (ps/search/milp/model.rs) + validation/refinement (ps/search/refinement/)
one selected plan Stage1ToStage3Run (ps/search/pipeline.rs:53) holding the selected PhysicalWorkloadPlan and Selection
Rejection with reason Rejection, RejectionCause (ps/lib.rs:804, :816)
Selected-plan validator and pricer PlanSelector (ps/lib.rs:1214), select_plan (:1188)

The MILP (ps/search/milp/model.rs, solved with HiGHS through good_lp): a binary candidate_selected per physical candidate configuration (:325), an integer topological_rank for acyclicity (:335), a binary connection per (consumer, input, compatible producer), a binary query-root assignment; each active consumer has exactly one producer per input (:388), each root is assigned once (:417), a selected candidate must be used (:637); the objective is a lower bound on local costs, tightened by cuts from evaluated plans and no-good cuts from rejected ones. The proposed plan is then validated and priced by PlanSelector (Stage 2 + pricing + accuracy + executor compile check), so the search is a propose–validate loop.

Interfaces: designed vs implemented

Designed Implemented
Input the complete memo from Stage 1 (Stage 3 "only selects", #958 decision 2); accuracy targets, accuracy model and evidence, cost model, raw-data retention, compile check run_stage1_to_stage3_with (pipeline.rs:163) / _from_pass1 (:195) take the query roots and PlanningModels, and Stage 3 runs Pass 1, Pass 2 and the state-group expansion itself (route.rs, search_domain)
Search MILP over the memo (#888) memo MILP is the only route; SearchAlgorithm (ps/lib.rs:1139) has one variant, CandidateConnections, still exposed as --search in stage_pipeline
Output one plan, cheapest among the candidates one validated plan; optimal only when the last (Complete) pane-width round finishes within budget

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 Check (accuracy incl. size-dependent and failure-probability resizing, latency, raw-data retention), price under the cost model, select with the joint search; prune single candidates that fail every assignment before the search; reject whole plans
Input &PhysicalASAPMemoCandidates (only Stage 2's output: no Pass 1/2 or Stage 2 calls), entries, DataWorkload, PlanSelectionConstraints { accuracy, evidence, raw_data_retained }, PlanSelectionCostInputs (was PlanSelectionSettings: cost model, resource speed, measured operation costs, query horizon), PlanSearchSettings { exhaustive, deadline, max_evaluations } (no SearchAlgorithm; the total budget moves to the facade)
Output Selection { selected_plan_id, selected_candidates: SelectedCandidates, weight_confirmed_candidates, cost_by_plan, accepted_accuracy_by_plan, rejected_plans, pruned_candidates, pareto_front, pane_retention_ms (keyed by PhysicalCandidateId), search_report }. plan is deleted: the facade builds the one plan
Function select_plan(&physical, entries, data, constraints, cost_inputs, search). This replaces today's whole-workload select_plan over optimize_physical_asap
PR #1047 P2 (SearchAlgorithm), P5–P7, P8 (Selection field renames)

Review checklist

User's suspected problem: does the MILP find the optimal candidate?

1. Boundaries

2. Naming and over-abstraction

  • "price" names in Stage 3 (price_node ps/lib.rs:3717, PricedNode, NodePriceSource in ps/cost/measured.rs, supplier_price_options, direct_price_options) where the repo term is "cost".
  • "supplier" (SupplierCandidates, ps/search/candidates/suppliers.rs) and "producer" (compatible_producers) name one concept; PhysicalCandidate.local_cost is an objective weight; PlanSelector validates and prices, the MILP selects ([Refactor] Repo-wide naming survey: overloaded and misleading names #948).
  • pub use search as pass; (ps/lib.rs:297) gives one module two public paths.
  • ps/lib.rs is 11,440 lines: about 5,190 production lines (settings, reports, PlanSelector, statistics bounds, accuracy checks and resizing, per-node pricing, latency, sketch byte sizes) and about 6,250 lines of tests from :5194. Split by concern.

3. Stale code

4. "memo" / "cache"

5. Rules over memo groups

Cost and accuracy constraints

Efficiency

Input/output check with the DAG viewer

Related issues

Sub-issues: #888, #929, #928, #927, #879, #964, #963, #994, #993, #949, #673, #946, #947, #726, #945, #898, #754, #660, #615, #733, #791, #843, #896, #918, #349, #347, #322, #250, #436, #454.

Referenced: #1002, #1004 (enumeration and pricing every candidate DAG, under #1008), #917 and #919 (planner_fuzz tooling), #958 (layout), #948 (naming).

🤖 Generated with Claude Code

Activity

  1. added
    cost-modelCost formulas, statistics, ranking, and selection
    optimizerWorkload optimization and plan selection
    searchCandidate-plan search space and algorithms
    on Oct 28, 2026
  2. 7 remaining items

  3. zzylol commented on Oct 29, 2026

    @zzylol
    ContributorAuthor

    Found while reviewing #1047's Stage 3 inputs: two paths decide how accuracy is judged. PlanningModels.accuracy (the AccuracyModel) does it, and PlanSelectionSettings::ignore_accuracy_guarantees overrides that model. For review here: should the override instead be an AccuracyModel the deployment passes in, so that Stage 3 has one accuracy input (PlanSelectionModels.accuracy)?

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

    cost-modelCost formulas, statistics, ranking, and selectionoptimizerWorkload optimization and plan selectionsearchCandidate-plan search space and algorithms

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions