Repository navigation
feat(planner): Example 1 completion — identical-expression CSE, whole-expression top-k heaps, counter-sample weights, per-second plan-selection cost - #692
Merged
Conversation
zzylol
force-pushed
the
stack/g4-example1-completion
branch
from
October 14, 2026 19:51
9d9e7bf to
4009093
Compare
…ked value Pass 1's Top-K sketch update weighted every heap by the sample value. Over SQL rows that column is the one named `value`, or else the only numeric column: an Int64 key made the heap candidates fail to compose, and a key named `value` would have weighted the heap by the key. The update now reads the column the top-k ranks (the aggregate's measure over grouped rows, the sample value over per-series rows), the same column exact top-k sorts by. `ranked_column` moves from Stage 2 into the shared realization helpers so both stages use it.
…Stage 3 candidates Stage 1 Pass 2 now applies the identical-expression rule of Algorithm 2. For each composed Pass 1 candidate it finds the sharing groups (two or more nodes with identical input and computation semantics that are legal to share) and adds one candidate per non-empty subset of the groups, keeping the independent candidate. A subset that leaves out a group that the other shared groups already merge gives the same candidate as the larger subset, so it is listed once. Nothing is capped. The variants are ordinary candidates for Stage 2 and Stage 3. PromQL rows that carry the full series identity now declare (identity, timestamp) as a unique key, which makes scans and range selectors legal to share. Plan selection treats totals that differ only by floating-point rounding as a tie and keeps the earlier candidate. A shared NotMaterialized input is charged once per consumer, so it ties with the independent plan. Example 1 gets 9 logical candidates (3 Pass 1 x (1 + 2 Pass 2)) and Example 3a gets 972. stage_pipeline's --max-combinations still counts Pass 1 combinations; its document lists every Stage 1 candidate. The Example 1 and scalar-queries viewer fixtures are regenerated.
…e into the realization helpers Pass 1's whole-expression heap reuses the legacy search's keyed-additive rule, so the rule, its result types and schema_column_ref move from replacement.rs into realization.rs, which both use. Pure move.
…nter-sample weights
Pass 1 gains a whole-expression candidate kind. A top-k over a per-item
sum or count that is itself a target (one consumer, no filter or HAVING,
realizable by the keyed-additive rule) has its Count-Min and CountSketch
heap candidates replaced by whole-expression ones: one heap over the
inner aggregate's input, keyed by the ranked item and weighted by the
summed value. It absorbs the inner target, which is then not computed.
The outer top-k's partition keys are moved onto the input the heap reads.
Example 1's Q2 gets exactly three candidates: exact, Count-Min + heap and
CountSketch + heap. Enumeration skips choices that differ only in an
absorbed target, and PerComputationCandidates::combinations counts the
same set.
Count-Min needs non-negative weights. Pass 1 does not read the data
workload: when a heap's weights are raw samples of metric M, or plain sums
of them, it records the open requirement WeightDomain::SamplesOfMetric{M}.
Rate, other arithmetic and other functions end it, and no type is inferred
from a `_total` name. DataWorkload gains metric_types (declared Prometheus
types). Plan selection admits Count-Min over such weights only when M is
declared a counter; an unconfirmed requirement counts as unmet. The
planner pipeline's selected plan carries the confirmed requirement as the
proof NonNegativeWeightProof::CounterSamples, since the executor reads no
data workload.
Example 1 declares http_requests_total a counter, so its Count-Min heap
is now rejected only because a top-k output has no guarantee; without the
declaration it is rejected on its weights. Raw-sample precompute now
checks the whole-expression heaps. The Example 1 viewer fixture is
regenerated.
Plan selection now prices a candidate per second, so queries with different recurrences add up, and takes each query's recurrence: - A NotMaterialized node is charged once per consumer at the sum of their rates (a shared NotMaterialized input still costs what reading it separately does). - A MatAtQuery node is computed once at the combined rate of the queries reaching it; queries at the same interval run together. - A MatAtIngestion node is priced over one second of arriving rows, once per second. - One-off, scheduled and unknown queries are spread over horizon_s. ResourceCalibration gains horizon_s (default 3600 s) and cost_per_retained_byte_second, the price of state kept between computations. Every materialized node's kept state is charged per byte per second: twice its output at ingestion time (the window being built and the completed one), once at query time. Memory held only during a query gets no per-second charge. select_plan, run_stage1_to_stage3 and run_stage1_to_stage3_from_pass1 take the recurrences; the planner pipeline and stage_pipeline read them from the workload. The cost model is described in the stage3_plan_selection module docs. The viewer fixtures are regenerated.
Plan selection no longer charges a computation for the memory it holds while it runs (#682's per-computation peak-memory term, including the growing exact state of a query-time aggregate). Memory is charged only as state a materialized node keeps between computations: kept bytes times cost_per_retained_byte_second. The kept state is the node's output, or the values an exact aggregate keeps per group when that is more, so a kept exact quantile or distinct count is still sized by what it keeps. Peak capacity belongs to cost model 2 (#683). Every term is now a per-second cost; the per-computation memory coefficient is ignored. The tests that compared exact state against sketches now keep the state at query time, where its size is charged.
An exact sum or mean of relative-error values keeps the relative bound only over non-negative values. When the folded values are raw samples of one metric, or plain sums of them (an exact sum accumulator, a renaming, or a heap estimate whose weights carry the SamplesOfMetric requirement), Stage 1 now names the unknown sign by that metric, values_non_negative[M], and gates the bound on it. Plan selection binds it to 1 when the data workload declares M a counter; a gauge, an undeclared metric, rate, other arithmetic or a name such as _total leave it unbound, so the target stays unmet. Other folds keep the bare values_non_negative, which nothing binds.
…umerating stage_pipeline's --max-combinations (default 10^4) now counts Stage 1 candidates as they are enumerated, Pass 2's identical-expression candidates included, and stops with an error as soon as the count passes the limit; it never truncates. Exit status 3 (the viewer server's 422) is unchanged. The library stays uncapped: Stage 1 is exposed as a lazy iterator (stage1_logical_asap_optimization) and Stage 2 and Stage 3 run over the candidates a caller enumerated (run_stage2_and_stage3); run_stage1_to_stage3_from_pass1 enumerates them all. planning_stages takes the limit as an Option.
… directly The identical-expression rule tried all 2^g subsets of the sharing groups and dropped those that repeat a candidate. With nested groups most subsets repeat (14 groups: 16 384 subsets for 130 candidates). Sharing a set of groups also shares the groups it leaves as one node; that closure maps each subset to its candidate, and the distinct candidates are its closed sets. They are now enumerated directly (Next Closure: one closure per step and group), then put in the same binary-counter order, so the output is unchanged. Twenty nested groups give their 20 candidates quickly; a brute-force test checks the same candidates on small cases. Sharing groups are computed once per candidate, and a Pass 2 candidate names its shared groups by kind and number (Scan #1, TimeRange #2), so repeated kinds stay distinguishable. The viewer fixtures are regenerated.
…rmed weights The counter-sample requirement Stage 3 confirms was made a proof only in the planner pass, so run_stage1_to_stage3's selected_logical and the selected candidate's roots still carried the open requirement, which the executor rejects. Stage 3 now puts the plan on its output: Selection::plan is the selected candidate's roots with confirmed weights, and Stage1ToStage3Run::selected_logical returns the selected logical roots the same way. The pass reads Selection::plan.
…ertify Count-Min estimate_has_bound treated any weight domain but UnknownOrSigned as proven, so a Count-Min estimate over weights with the open SamplesOfMetric requirement got its bound as if the weights were proven non-negative. Only NonNegative now counts. Such an estimate instead gets its local bound gated on the metric's sign, values_non_negative[M], which plan selection binds to 1 when M is declared a counter and leaves unbound otherwise.
… to rounding Shows that the tie tolerance is needed: the shared-input candidates' totals differ from the independent candidate's in the last bits, and plan selection reports them as tied and keeps the independent one. Also drops stale wording in the acceptance helpers.
Applying a candidate's label (the groups it shares) gives the same roots the direct enumeration returned, sharing exactly those groups.
The sign of one metric's samples gates a relative bound as a factor; a binder supplying 0 would have read as a perfect bound. Plan selection now binds a sign statistic only to 1 (proven) and leaves any other value unbound.
The Example 3 acceptance spec and the stage viewer guide now give 3a's 972 candidates and its all-KLL selection, 3b's KLL selection tied with DDSketch, the per-second totals, and the shared-input plans that tie with the independent one.
zzylol
force-pushed
the
stack/g4-example1-completion
branch
from
October 14, 2026 23:16
4009093 to
d542c30
Compare
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.
Why
#509's Example 1 could not run end to end on the planning stages:
This PR completes Example 1 (consolidation group G4) under the user's decisions on these points.
What
DataWorkload. Plan selection confirms it from the newDataWorkload.metric_types; unconfirmed counts as unmet.value, plans correctly.ResourceCalibrationgainshorizon_sandcost_per_retained_byte_second.stage_pipeline --max-combinations(default 10^4) counts Stage 1 candidates, Pass 2's included, while enumerating, and stops with an error past the limit. Exit 3 / server 422 are unchanged.How
asap_aware_cse).ir::cseinterning identifies the sharing groups. Sharing a set of groups also shares every group it leaves as a single node. The distinct candidates are therefore the closed sets of that closure, and they are enumerated with Next Closure (at most one closure per step and group) instead of trying all 2^g subsets. They are emitted in the subsets' binary-counter order.stage1_logical_asap_optimization), andrun_stage2_and_stage3runs over what a caller enumerated. The devtool counts as it consumes the iterator; the library collects all candidates.ComputationCandidate::WholeExpression) reuse the keyed-additive rule, which moves torealization.rsalong withranked_column. The outer partition keys are re-indexed onto the heap's input. Enumeration andcombinations()skip choices that differ only in an absorbed target.WeightDomain::SamplesOfMetric { metric }. The sign of M's samples is the statisticvalues_non_negative[M], which gates a bound as a factor. Stage 3 binds it only to 1, when M is declared a counter, and leaves it unbound otherwise.stage3_plan_selectionmodule docs):horizon_s(3600 s).Key code interfaces added / modified
crates/asap-aware-mapping/src/asap_aware_cse.rs(new),lib.rscrates/asap-aware-mapping/src/pass/stage1_to_stage3.rs,pass/mod.rscrates/asap-aware-mapping/src/per_computation_candidates.rscrates/asap-aware-mapping/src/stage3_plan_selection.rs,analytical_cost.rscrates/asap-aware-mapping/src/accuracy/evidence.rs,composition.rscrates/types/src/post_asap/sketch.rs,workload.rs,ir/schema_support.rscrates/devtools/src/planning_stages.rs,bin/stage_pipeline.rsUsage, end to end (checked with a scratch test over Example 1 at
80fc72e1): the library path a caller such asstage_pipelinetakes, from the user's workload to the selected plan.Before this PR
valuethe heap was weighted by the key.stage_pipelinerefused workloads by their Pass 1 combination count only.After this PR
http_requests_totaldeclared a counter, Count-Min is rejected only because a top-k output has no guarantee; without the declaration it is rejected on its weights.run_stage1_to_stage3getSelection::planandselected_logical()with confirmed weights, so the executor accepts a selected Count-Min heap.Totals are cost per second under the illustrative calibration. Screenshots: not applicable.
Verification
value.--all-features --locked -D warningspass, and nextest over the workspace passes 1650 tests (46 skipped).Limitations and follow-ups
select_planusesillustrative_calibration(); a pluggable calibration is G9.evaluation_rateignores alignment and phases, and one-off queries use the largest invocation count.weighted_topk_bindingstays ignored on the heap estimate's output shape (fix(ir): top-k sketch readouts return selected rows; Example 1 expectations follow planner output #579).Supersedes #588, #589, #640, #593 and #594, which are left open.