Repository navigation
feat(plan-selection): group selection MILP over execution contexts as Stage 3's default search (#1095) - #1144
Merged
Merged
Conversation
Open
16 of 61 tasks
zzylol
force-pushed
the
feat/1095-group-milp
branch
from
October 31, 2026 17:28
857e93c to
7306142
Compare
zzylol
changed the base branch from
refactor/stage-boundaries
to
feat/1095-stage3
October 31, 2026 17:28
This was referenced Oct 31, 2026
zzylol
force-pushed
the
feat/1095-group-milp
branch
from
November 2, 2026 23:02
b6eb71d to
b1c9232
Compare
…hind ASAP_STAGE3_SEARCH=milp (#1095) Step 1 of #1095's solving strategy: one MILP with a warm start. The owner-model pieces from tag stage3-owner-milp-redesign are restored and the owner/instance model is replaced by execution contexts: each query evaluation, the ingestion pipeline I (NotMaterialized ingestion-time candidates) and each shareable (materialized) candidate's own inputs. - groups.rs (was owners.rs): RunKind, ResultSlot (group roots split by self-read depth; private nodes their own slot), execution contexts and their options; dominance, per-candidate usage and accuracy weights kept. Scan windows are costed at every union of the window kinds read within one context, as plan_cost prices them. - milp/model.rs: p[ctx, c], x[m], a[q, m], rows 1-9, slot links for inputs over several result slots, order ranks within and across contexts, latency in scaled units. - milp/cost_terms.rs: run terms (plain, producer-dependent, exact scan window sets), schedule-group rates, held memory, raw retention and pane retention levels. - milp/warm_start.rs: each query's cheapest plan, then greedy sharing, costed exactly by the completion. - milp/decode.rs: one node per (context, computed candidate) and per selected shareable candidate. - asap-cost: a NotMaterialized ingestion-time node runs once per second for every consumer; a NotMaterialized node a materialized node computes is held until that node's slowest reader finishes. The old engine stays the default.
…ndow; window-independent scans keep their usage per window (#1095)
…e after rebasing onto the Stages 0–2 milestone (#1095)
…gap diagnosis, tighter big-M bounds (#1095 §5 3a, §6.1) (#1155) Each connected component of the selection MILP is solved alone and joined before the single decode, realization and plan_cost check. An lp-diagnosis feature (devtools only) attributes the LP gap by row and variable family. Two big-M bounds are tightened. No selected plan or cost changes.
…ries (#1095 §8) (#1156) Measured that candidates of one operator node never share an input context on the measured workloads (no same-timing pairs), so the §8 shared-input-context step has nothing to share. The 44 s build of rangeheavy-t8-t5 largest round was 96% accuracy checks; a candidate built outputs are now kept across pair checks and queries (except outputs built across a cycle of links), and duplicate exact stand-ins are skipped. Weights, excluded pairs, plans and costs unchanged; that round builds in 7.6 s.
… selection MILP (#1095) (#1157) Brings feat/1095-stage3 (#1151 refresh groups and cost rule) into the prototype and drops its once-per-second ingestion hook. The MILP has one ingestion context I_r per refresh window (unbounded views alone), so a shared stateless ingestion node is charged once per refresh window, as asap-cost charges it. workload-01 costs 1.6% more: its old plan under-charged a scan read by chains of three refresh windows. Part of #1095.
…up MILP after rebasing onto main The group MILP commits were written before #1163 renamed PaneChain to SummaryTumblingWindowChain and pane_width_ms / pane_count / newest_panes / pane_retention_ms to their tumbling-window names; rename their uses, and rustfmt the lines the rebase resolved.
… C6) select_plan runs the group MILP unless ASAP_STAGE3_SEARCH=old. Inputs the MILP does not price yet (cost models 2 and 3, or a measured operation cost table without a frozen calibration factor) are an UnsupportedCostModel error; there is no fallback to the old engine.
…art 2) in the group MILP #1176 renamed the remaining pane helpers (pane_candidates, kept_pane_chains, panes_of, retained_panes, the pane test helper, ...); rename their uses in the group MILP's code and tests.
zzylol
force-pushed
the
feat/1095-group-milp
branch
from
November 2, 2026 23:21
b1c9232 to
b7b4ce3
Compare
zzylol
marked this pull request as ready for review
November 2, 2026 23:21
3 tasks
milindsrivastava1997
added a commit
that referenced
this pull request
Nov 3, 2026
…is infeasible Candidates that cannot be priced (e.g. a source with no arrival rate) are dropped before the MILP. When that leaves it infeasible, the rejection said only "infeasible", hiding the missing source fact. The rejection now appends each distinct pricing error. Fixes the DAG viewer test test_a_plan_without_a_rate_fact_names_the_missing_fact, red on main since #1144. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 3, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Landing on main (2026-11-02)
This PR now goes to main, and it makes the group MILP Stage 3's default search. The code is merged as it stands. The review of the group MILP (#1015, section "Review of the group MILP") lists what to revise next:
note_inexactgating the cost-equality check;ResultSlotworkarounds;Changes since the draft:
Rebase onto main. Rebased onto main after feat: refresh windows: refresh groups and a per-window ingestion charge (#1151, lands #1152–#1154 on main) #1182 (Refresh ingestion-time views that share a refresh window in one run, and charge a shared stateless ingestion node once per run #1151's refresh windows, which feat(plan-selection): one ingestion pipeline per refresh window in the selection MILP (#1095 §2.5 O3(b)) #1157 builds on), refactor: rename every remaining pane name to the tumbling-window wording (#1163, part 2) #1176 (Rename PaneChain to SummaryTumblingWindowChain, and every pane name to the tumbling-window wording #1163 part 2) and test: ignore tests that plan under a wall-clock budget (#1175) #1181.
Two rename fix-ups. They carry Rename PaneChain to SummaryTumblingWindowChain, and every pane name to the tumbling-window wording #1163's tumbling-window names, parts 1 and 2, into this PR's code.
New commit "the group MILP is Stage 3's default search ([Feature] Stage 3: no execution-scope copies, exact candidate-level MILP, one cost formula #1095 C6)".
select_planruns the group MILP unlessASAP_STAGE3_SEARCH=old. Inputs it does not price yet returnErrorSelectingPlan::UnsupportedCostModel, with no fallback to the old engine:ASAP_STAGE3_SEARCH=milpis no longer needed.Tests under the new default. Seven tests fail with the group MILP, and #1181 already ignores all seven, as nondeterministic under wall-clock budgets (#1175). Each passes with
ASAP_STAGE3_SEARCH=old. The gaps they hit, also listed on #1015:runtime_installtests: an accuracy model stricter than the built-in rule (HeapPlansOnly) is checked only after the solve, with no re-solve.stage_pipelinetests of cost models 2 and 3: nowUnsupportedCostModel.raw_data_windows_of_unsupported_shapes_are_refused: the query is still refused, but the MILP reportsInfeasiblewithout the concrete raw-data-window reason.The release-mode snapshot tests (
corpus_snapshots,sketch_plan_snapshots,#[ignore]) were not regenerated under the new default. Their selected plans may change.Why
#1095 §2.0 replaces the owner/instance selection MILP with a group-level MILP over execution contexts: each query evaluation, the ingestion pipeline
I, and each selected shareable candidatem. This PR is step 1 of the design's solving strategy (one MILP with a warm start, then measure), so the design can be judged on numbers before any decomposition (3a–3c). It was first a draft for visibility; it now lands on main as the default search (see "Landing on main" above).What
ASAP_STAGE3_SEARCH=oldruns the old engine) once per planning round, from a warm start, decodes it toDagOfSelectedCandidates, realizes it once, and checksplan_costandsatisfies.plan_costof the decoded plan. It solves to optimality and matches today's engine. ratio-of-quantiles now plans at 2.407e-6, down from 5.27e-6 under the owner model. Planner aborts with bad_alloc under 8 GB on random-mixed-seed-862 workload-01 when raw data is retained #1036 workload-01 plans within 8 GB (0.9 GB peak) at 1.417e-6 (owner model: 2.006e-6).ASAP_STAGE3_REPORT=<path>appends one JSON line per Stage 3 run: size per family and per scope (themcontexts apart), timings, warm-start cost, objective,plan_costand the dual bound. This is for measurement only.How
groups.rs(wasowners.rs): dominance, per-candidate usage and accuracy weights come from the owner model. New in this file:RunKind(per context, ingestion, shareable);ResultSlot: what "one candidate per memo group per context" ranges over;execution_contexts;milp/model.rs:p[ctx, c],x[m], reacha[q, m], rows 1–9, slot links for inputs over several slots, order ranks inside and across contexts, latency in scaled units, and the solve.milp/cost_terms.rsholds the run terms:AND(run, p[ctx, c']);δ_S.It also holds the schedule-group rates
z, held memory (H_query,H,W), raw retention levelsvand pane retention levelsπ.milp/warm_start.rs: each query's cheapest plan, then greedy sharing, over several query orders. Every candidate plan is costed exactly by the completion, which gives the cheapest continuous values for given binaries.milp/decode.rs: one node per (context, computed candidate) and one per selected shareable candidate.asap-cost, one hook (
execution_demand::computations): aNotMaterializedingestion-time node runs once per second for all consumers. This is §2.0'sI. Before, it was charged once per consumer.Key code interfaces added / modified
Crate-private except the following, all restored from tag
stage3-owner-milp-redesign:crates/plan-selection/src/report.rs
crates/types/src/stages/pruning.rs:
PruneReason::DominatedBy(PhysicalCandidateId)andPruneReason::UsageNotCostableare new; Stage 3 prunes these before the MILP.crates/cost/src/lib.rs:
retained_panesandmaintained_window_msarepubagain.Usage:
Major algorithm changes
Stage 3 selection: group MILP over execution contexts (behind the switch)
m_sinstances, each owning a region. On ratio-of-quantiles it recomputed the stateless subtree per accumulator owner (5.27e-6 against 2.41e-6 for today's engine). Planner aborts with bad_alloc under 8 GB on random-mixed-seed-862 workload-01 when raw data is retained #1036 workload-01 needed 584k variables and stopped at the deadline at 2.006e-6.x[(s, j)]instances, per-owner linkse/band symmetry rows. Now:execution_contexts);p[ctx, c]with one candidate per slot per context;x[m]charged once;T[q, m]per query-timem) and A1 accuracy per run;asap-costnumbers;σexact from both sides,δ_Sper set), costed byasap-costat each union, so they need no monotonicity assumption.milp::model::tests::*. Each test solves, decodes, validates, realizes and checks objective ==plan_cost. Together they cover:MatAtQueryresult'sNotMaterializedinput run once;avgshape computed once;MinimumQueryReaders;--exhaustive), the MILP equals the best enumerated plan on example1 (4.3770), ratio-of-quantiles (2.4071e-6) and rangeheavy-t5-q05-q09 (3.9901e-6).mcontexts are 35–60 % of the variables.Measurements (300 s budget, accuracy enforced, aws-fargate, raw data retained; workload-01 under
ulimit -v 8388608)The facade runs Stage 3 once per pane-width round inside the 300 s budget. Each row is the round whose plan was selected; "largest round" is the biggest model of the run.
In the table,
m= the shareable-context share of the variables / rows / binaries. Warm and Final are costs per second. Old engine is today's engine on this branch, same flags.m)plan_cost/satisfiesmvars), build 45 s, no dual boundulimit -v 8388608)mvars)Owner model (/mydata/milp-decode-runs, same flags), for comparison:
Every selected plan's
plan_costequals the MILP objective, which is recomputed from the incumbent's binaries. The only term encoded inexactly on these runs is one scan in workload-01 read at more than 4 window kinds; it is charged at the widest union, so its cost is an upper bound.Design points
Held
x[m]once,Icharged once, "used only if read" and per-run latency/accuracy all hold exactly againstplan_cost.avgsubtree is computed once: 2.41e-6, against 5.27e-6 under the owner model.Did not hold or was underspecified. In each case this PR takes the conservative exact option:
"The memo group graph is acyclic" does not hold on this base. A widened scan's
Projectis a root of the scan's own group; fix(memo): an unwidened scan no longer becomes a widened reader of its own group #1138 removed only the unwidened case. Under one-candidate-per-group theProjectand the scan it reads would need one slot (infeasible).Projectreading its own group).Project∘Projectplans.a_widened_scan_reading_its_own_memo_group_plans_exactly."One candidate per memo group": an implementation has private nodes (
is_implementation_root == false) of its own group. These are their own slots.MinimumQueryReaders(2)on candidate 43 hold falsely, andDagOfSelectedCandidates::validaterejected the decoded plan.eper multi-slot input. It is one-hot on the reader, and row 4 countse.Row 7 double-counts when
L[m]includesT[m, m']:qreachesm'directly too, soT[q, m']counts it again. ImplementedL[m]=m's own run plus its context's runs, and oneT[q, m]per query-timemthatqreaches. This isplan_cost's "each reached query-time node once".Rows 3–5 admit cross-context self-justifying cycles:
x[m] ≤ p[m', m]andx[m'] ≤ p[m, m']for two shareable candidates of one group. Order ranks were added on the graph of shareable runs and runs ofI, for cyclic components only."
Iruns once per second for every reader" was notplan_cost's semantics:computationscharged once per consumer. Hook inasap_cost::execution_demand::computations: aNotMaterializedingestion-time node is(1, 1.0). (The slowest-reader working memory was already in fix(cost): hold a materialized consumer's working-memory copy for its longest reader (#1095) #1134.)Scan windows: the design's 2-D thresholds assume monotone usage. They were replaced by an exact encoding:
σ_Wis exact from both sides per window kind;δ_Sis the indicator of "exactly the kinds inS";asap-cost, withplan_cost's rule for an unbounded read (the trailing widest bounded window).This needs no monotonicity, for up to 4 kinds per scan per context (beyond that the widest union is charged: one scan on workload-01).
"One group = one result, so input statistics do not depend on the producer" does not hold for scans (output rows follow the window) or for candidates whose outputs differ only by child ids.
AND(run, p[ctx, c'], producer gives o).Numerics: with aws-fargate, latency in ms is below HiGHS's absolute tolerances. On
shared_sum, HiGHS returned an objective of 9.5e-14 for a plan costing 2.97e-13, and claimed optimal. Fixed in two ways: latency variables count in scaled units (largest possible latency ≈ 1,000 units), and the incumbent's objective is recomputed from its rounded binaries."One MILP solve": the facade runs Stage 3 once per pane-width round inside the 300 s budget, which gives 11–16 solves per run. The selected round is usually an early, small one.
What a decomposition step would help with
mcontexts are 47–63 % of the variables (t8-t5 largest round: 71.7k of 114k). So the open point "share one input context per operator node across its materializations" would cut size more than 3b.Exactness checks run
--exhaustive) equal on example1, ratio-of-quantiles and t5-q05-q09.satisfiescorpus gate were not run.Before this PR
refactor/stage-boundarieshas only today's engine (#1133 removed the owner-model redesign).After this PR
ASAP_STAGE3_SEARCH=milpruns the group MILP. It is exact againstplan_costand the reference pricer on the memo examples, and it plans #1036 workload-01 within 8 GB. The old engine stays the default.Verification
cargo fmt --all --check;cargo clippy --workspace --all-targets -- -D warnings.cargo test -p asap-plan-selection --lib: 312 passed.cargo test -p asap-cost.cargo test -p asap-devtools --test devtools -- milp_search: 3 passed.stage_pipelineandASAP_STAGE3_REPORT.🤖 Generated with Claude Code