fix(planning): derive the fallback window implementations from the query - #696
Closed
zzylol wants to merge 2 commits into
Closed
fix(planning): derive the fallback window implementations from the query#696zzylol wants to merge 2 commits into
zzylol wants to merge 2 commits into
Conversation
…adence
A snapshot that prices no window implementation for a query got one
synthesized here, hardcoded to a lookback-wide tumbling window with a
lookback-wide pane. The query's own evaluation cadence was parsed a few
lines above, used for lifecycle costing, and then dropped.
The result is a plan whose answer only changes once per window. A
workload of two 5m-lookback quantiles evaluated every 30s planned as
window 300 / slide 300 / tumbling / Pane{300}: the state advances every
five minutes while the queries run every thirty seconds. The retained
count followed it down to 2, which is arithmetically right for a
300s pane and useless for a 30s cadence.
Derive the shape instead. When the cadence is shorter than the window
and divides it, slide by one evaluation interval and store panes of that
width; otherwise keep the previous tumbling shape. The same workload now
plans as window 300 / slide 30 / sliding / Pane{30}, retaining 11 states
-- ten panes covering the lookback plus the one still filling.
This derives a shape, never a price. `ImplementationCostEvidence` is
measured evidence: its `weighted_cost` doc puts pricing update CPU,
query-time merges, retained memory, storage, scans and network on the
evidence producer. So this does not synthesize a second candidate to
rank against the first -- a lone candidate is chosen by a `min_by` over
one element, where the cost cannot change the outcome. Ranking Pane
against FullWindow still requires a snapshot that supplies both with
their own priced evidence, and `window_candidates` already carries them
untouched when it does.
Two existing assertions encoded the old default's pane width. The
snapshot they load evaluates every 10s over a 1m lookback, so its stored
pane is now 10s; both readouts keep their 1m `readout_lookback_ms`, and
every other property those tests assert is unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The derived fallback built a single candidate from `time_selection.lookback`. A query carries one range selector per operand, and they need not agree with the declared lookback or with each other. The consequence is silent. Under `hybrid_execution`, a selected state whose window has no candidate is filtered out of selection entirely, and the surviving candidates are then narrowed to the selected window before validation. So `sum(sum_over_time(a[1m])) / sum(sum_over_time(b[5m]))` under a 1m lookback kept `a` and dropped `b` to exact execution, with nothing reported and no error raised. Derive one candidate per distinct range-selector window instead, each shaped by the evaluation cadence as before. `window_implementation_id` reaches lifecycle estimates and cost manifests, so a query with several windows suffixes it per window; a single-window query keeps the snapshot's identity untouched. Three assertions changed, all of them pinning the previous artifact rather than intended behavior: - `composable_binary_binds_independent_source_windows` hand-supplied a 5m candidate so `b` would survive. The derivation now covers it, so the workaround is gone and both operands keep their own range. - `composable_binary_retains_summary_sibling_of_prometheus_filtered_subtree` asserted a filtered denominator stays a typed residual. Nothing about the filter forced that -- the missing 5m candidate did. It now gets a summary over its own filtered population, which the renamed `composable_binary_summarizes_each_prometheus_filtered_operand` pins, filter and range together. - `counter_materialization_manifest_prices_owned_state_and_distinct_native_alternative` required an `exact_backend` component. Its 5m operand landed there for the same reason and is now summarized. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 12, 2026
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
A workload of two 5m-lookback quantiles evaluated every 30s compiled to a plan
whose state advances once every five minutes:
Nothing rejected it, because nothing had planned it. The query's evaluation
cadence was parsed, used for lifecycle costing, and then dropped before the
window shape was chosen.
Worse, and found while reviewing the first fix: that fallback was a single
candidate built from
time_selection.lookback. A query carries one rangeselector per operand, and under
hybrid_executiona selected state whose windowhas no candidate is filtered out of selection outright. So
sum(sum_over_time(a[1m])) / sum(sum_over_time(b[5m]))under a 1m lookback keptaand silently droppedbto exact execution — no error, no report.What
The fallback window implementations — synthesized when a snapshot prices none
for a query — are now derived from the query itself: one per distinct
range-selector window, each shaped by the query's evaluation cadence.
Snapshots that supply
window_candidatesare unaffected.Plan-compile time only. No change to the wire contract, to serving, or to any
snapshot that carries its own priced candidates.
How
compiler.rs'sunwrap_or_elseproduced one lookback-wide tumbling window witha lookback-wide pane, ignoring both the range selectors in the query and the
evaluation_interval_msparsed ~60 lines above.derived_window_candidatesreplaces it.
range_selector_windows_secswalks the canonical pre-ASAP expression forTimeRangenodes, so each operand gets a candidate for its own range.window_implementation_idreaches lifecycle estimates and cost manifests, so amulti-window query suffixes it per window while a single-window query keeps the
snapshot's identity byte-for-byte.
Within one window the shape follows the cadence:
interval, store
Pane { pane_secs: slide_secs }, frameworkSliding;The guards are the validator's own rules, so a bad derivation would be a compile
error rather than a silent plan:
window_secsmust equalPlanningQuery::window_secs;WindowMaterializationLayout::validaterequires the pane to divide both windowand slide (45s into 300s has no such pane); the framework/layout table admits
Tumbling + PaneandSliding + Pane.pane_secs == slide_secsis thecoarsest legal pane for a given cadence — the fewest query-time merges.
This derives a shape, never a price.
ImplementationCostEvidence::weighted_costis explicit that the evidence producer prices update CPU, query-time merges,
retained memory, storage, scans and network. So this does not synthesize a
second candidate to rank against the first: a lone candidate is chosen by
complete_summary_candidate_estimate'smin_byover a one-element list, wherethe cost value cannot change the outcome. Ranking
PaneagainstFullWindowstill requires a snapshot supplying both with their own evidence — see
Limitations.
Before this PR
Every query without supplied candidates got exactly one candidate: a
lookback-wide tumbling window, whatever its cadence and whatever ranges it
actually selects.
num_aggregates_to_retainfollowed —(lookback + staleness) / pane + 1withpane == lookbackis always 2. Any operand whose own rangediffered from the declared lookback lost its summary without a word.
After this PR
The same workload plans as window 300 / slide 30 / sliding /
Pane{30},retaining 11 states. Each sample updates exactly one 30s pane
(
worker.rs's non-FullWindowbranch), and a readout composes ten of them.Each operand of a multi-range query keeps its own window and readout:
sum(sum_over_time(a[1m])) / sum(sum_over_time(b[5m]))now installs both, wherebefore
bfell back to exact execution.A single-range query whose cadence equals its lookback, or does not divide it,
is planned exactly as before.
Evidence
Execution example — the reported workload, from
five_minute_lookback_evaluated_every_thirty_seconds_slides_by_thirty:Execution example — derivation across cadences, from
derived_window_candidate_follows_the_evaluation_cadenceandderived_window_candidate_stays_tumbling_without_a_dividing_cadence:Pane{30}Pane{300}Pane{300}Pane{300}Pane{300}Performance measurement: not applicable — no runtime path changes. Choosing
between layouts on measured cost is explicitly out of scope here (Limitations).
Screenshot: not applicable.
Verification
derived_window_candidate_follows_the_evaluation_cadence— a 30s cadenceover a 5m lookback yields sliding / 30s slide / 30s panes.
derived_window_candidate_stays_tumbling_without_a_dividing_cadence— acadence at, above, or not dividing the window keeps the previous shape.
derived_window_candidate_shapes_are_accepted_by_validation— every shapethis function can emit passes
validate_window_implementations, so a badderivation cannot reach a plan.
supplied_window_candidates_are_not_replaced_by_the_derivation— a snapshotcarrying its own priced candidates keeps them verbatim.
retained_state_count_follows_the_derived_pane_width— end to end, theretained count is derived from the pane width (six 10s panes for a 1m
lookback, plus the one filling → 7).
five_minute_lookback_evaluated_every_thirty_seconds_slides_by_thirty— thereported case, end to end.
composable_binary_binds_independent_source_windows— reused as themulti-range regression: it no longer hand-supplies the 5m candidate its
second operand needed, and both operands keep their own range.
cargo test -p control_plane— 807 passed, 0 failed (768 lib =762 pre-existing + 6 new, plus 31 api + 8 integration).
cargo test -p data_plane --libdoes not compilein this checkout —
asap_sketchlibis missingaccepts_standard_updates(
univmon_accumulator.rs:48) andquantile_interpolated(
summary_executor.rs:1053). Pre-existing and unrelated (this PR touches onecontrol-plane file), but it means no data-plane evidence is offered here.
cargo fmt -p control_plane -- --checkclean;cargo clippy -p control_plane --all-targetsreports no new warning in the changed file.than intended behavior. Called out individually because two of them turn an
exact path into a summary path:
composable_binary_binds_independent_source_windows— hand-supplied a 5mcandidate so its second operand would survive. The workaround is deleted;
both operands now keep their own range, with 10s panes for the 10s cadence.
composable_binary_retains_summary_sibling_of_prometheus_filtered_subtree— asserted a filtered denominator stays a typed residual. Nothing about the
filter forced that: the missing 5m candidate did. Verified directly that the
installed state carries
{job!="x"}and the binding'spopulation_filter_canonicalmatches, so the query reads a summary over itsown filtered population rather than an unfiltered sibling. Renamed to
composable_binary_summarizes_each_prometheus_filtered_operandand now pinsfilter and range together. The residual path itself loses this test's
coverage — it still exists, but no test in this file exercises it now.
counter_materialization_manifest_prices_owned_state_and_distinct_native_alternative— required an
exact_backendcomponent. Its 5m operand landed there for thesame reason and is now summarized.
Architectural decisions
Derive from the query, not from the declared lookback.
time_selection.lookbackis the workload's declared range; the range selectors are what each operand
actually computes over, and each becomes its own materialization. Rejected
alternative: keep one candidate and widen it to the largest range. That would
give short-range operands a state far wider than they read.
Derive the shape, not the price. Rejected alternative: emit several
candidates (
Pane{30},Pane{10},FullWindow) with synthesized costs so theexisting ranking has something to compare. The ranking machinery is genuinely
ready —
RealizationProvider::windows→with_window_implementation_costs→complete_summary_candidate_estimateis wired and live — but feeding it coststhis compiler invented would be fabricating measurement, against the explicit
contract on
weighted_cost. A lone candidate needs no price to be selected,which is why fixing the shape needs no new evidence.
Pane, not FullWindow, for the derived sliding shape. Both are legal Sliding
layouts. Preferring FullWindow is a write-amplification-versus-read-amplification
tradeoff (each sample updating
window/slideoverlapping states versus onepane), i.e. exactly the comparison that needs two priced quotes. With one
candidate available, the pane form is chosen because it does not multiply
per-sample update work.
pane_secs == slide_secs. The validator requires the pane to divide theslide, so any legal pane is at most one slide wide; the widest one is the
cheapest to read. Note
realization.rs's module doc forbids a realizationprovider from inferring pane width from slide — this derivation lives at
snapshot ingestion instead, which is where the cadence is known.
Limitations and follow-up
FullWindowcompared withPanemust supply both inwindow_candidates,each with its own
ImplementationCostEvidence. The control plane could becomethat producer by composing the snapshot's supplied lifecycle unit costs
(
build,maintenance_per_update,read,retention_per_second) with eachlayout's structural counts — update fanout, merges per read,
retained_state_count— which would be a cost model rather than inventedmagnitudes. It is deliberately not in this PR: it changes which layout every
sliding query selects, driven by a formula a reviewer has to check line by
line, and folding it in behind a shape fix would hide exactly the part that
needs reviewing.
slide == window.maintenance_runtime.rs:1108and:1269reject a sliding cohort formaterializations with a
derived_input. Live-ingest materializations (thereported case) are unaffected, and the gate fails closed with a clear message
rather than computing something wrong. Lifting it is not a one-line change and
is not attempted here:
:1163filters source windows with(window.0 - pane_origin_ms).rem_euclid(width), wherewidthis the fullwindow, so a sliding cohort whose sources arrive every slide would have nine
of every ten windows silently skipped. That filter, the two gates, and the
readout semantics for overlapping states need one change with its own
validation — and
cargo test -p data_planedoes not compile in this checkout,so none of it could be verified here.
window_implementation_idis not renamed by the derivation. It is thesnapshot's identity for its priced implementation, so a demo value of
backend-tumbling-v1now labels a sliding candidate. Confusing to read, butrenaming deployment-supplied identity here would be worse.
Extension( "backend.exact-hierarchical-rollup.v1")framework and its own evidence.Human review — do not complete with an agent
🤖 Generated with Claude Code