Repository navigation
fix(stage2): a window equal to its interval is kept at ingestion time; whole-window builds end on the epoch grid - #899
Merged
Conversation
…hole-window builds end on the epoch grid A whole-window build (one window per evaluation) may now be maintained at ingestion time for readers declared with a fixed interval and no phase, as a pane chain's readers already are: a reader of unknown phase is taken to run on the epoch grid. The build must end on the grid of its window's width anchored at the epoch, where the runtime builds it: the interval and any known phase are multiples of the window. So m[5m] every 5 minutes (PromQL or SQL) is one tumbling window kept at ingestion time and plans without raw data, and m[20s] every 20 s at phase 5 s no longer reads a window off by 5 s (it uses 5 s panes). Closes #893 Closes #828
…otFixed, wording on undeclared phases
zzylol
force-pushed
the
fix/window-equals-interval-tumbling
branch
from
October 22, 2026 19:34
5fe118a to
940ad49
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
A query whose window equals its repeat interval had no ingestion-time plan, in PromQL and SQL:
quantile_over_time(0.99, m[5m])every 300 s, orSELECT region, approx_percentile_cont(latency, 0.99) ... WHERE ts >= now() - INTERVAL '5 minutes' GROUP BY regionevery 300 s. A deployment that keeps no raw data therefore got no plan at all. In the 2026-10-22 W-RollupHeavy 8-user run without raw data, 26 of the 30 final components had no plan for this reason (#893).The same rule also accepted a whole-window build at a phase off the epoch grid (
m[20s]every 20 s at phase 5 s), which a runtime builds on the epoch grid and so reads 5 s early (#828).What
Stage 2 keeps a whole-window build at ingestion time when every evaluation of its readers ends on the grid of the window's width anchored at the epoch:
So
m[5m]every 5 minutes is one tumbling 5-minute window maintained at ingestion time and read at each evaluation, as a one-pane chain would be.m[20s]every 20 s at phase 5 s, or every 30 s, is not kept whole; Pass 2's 5 s or 10 s panes serve it. Pass 2 is unchanged.How
ingestion_eligibility(physical-asap-optimization) collects the readers' interval and known phase (FixedIntervalorFixedIntervalAt; others stayWindowNotFixed), requires one interval and agreeing known phases, and then checksinterval % window == 0andphase % window == 0. Off the grid it returns the new prune reasonWindowOffEpochGrid.Key code interfaces added / modified
crates/physical-asap-optimization/src/lib.rs(crate-private)No public interface, wire or file format changes.
Usage
Stage 1 lowers each
WHERE ts >= now() - INTERVAL 'w'toTimeRange(w)and picks KLL; Pass 2 builds 1-minute panes for the queries whose window exceeds their interval. Stage 2 callsingestion_eligibilityon the whole-window KLL of Q3 (5 minutes every 300 s, no phase): interval 300000 ms % window 300000 ms = 0, so it is offeredMatAtIngestion(Memory)(before:WindowNotFixed). Stage 3 selects:Before this PR the same run ended with
NoValidCandidate: "453 Stage 1 candidates scan raw data at query time in every materialization, but the deployment does not retain raw data".Major algorithm changes
Stage 2 ingestion eligibility of a whole-window build
quantile_over_time(0.99, m[5m])every 300 s (fixed_interval, no phase): Pass 2 offers no pane chain (w = gcd(W, I) = Wgives one pane, and chains needpane_count >= 2), and Stage 2 pruned the whole-window build asWindowNotFixedbecause no phase was declared. Without raw data, no plan. Converselym[20s]every 20 s at phase 5 s was accepted and read(t-25s, t-5s]on a runtime that builds on the epoch grid.ingestion_eligibility): 1. every reader isFixedIntervalAtwith the same (interval, phase), elseWindowNotFixed; 2. the build's child is an unshifted rangeTimeRangewithW <= I, elseWindowNotFixed.FixedInterval(I)orFixedIntervalAt(I, p), elseWindowNotFixed; all shareI > 0, and declared phasesp mod Iagree, elseWindowNotFixed; 2. the child is an unshifted rangeTimeRange(W)withW > 0, elseWindowNotFixed; 3.I % W == 0and (no declared phase orp % W == 0), elseWindowOffEpochGrid.t = p + kIis then a multiple ofW, so the window(t-W, t]is exactly one tumbling window of the epoch grid the runtime maintains; an unknown phase is taken to be on the grid, the assumption pane chains already make (builds_one_pane_per_evaluation, the epoch check in the chain branch). Tests:a_whole_window_build_needs_evaluations_on_the_epoch_grid_of_its_window(unit: no phase, 2× interval and phase 3 min accepted; phase 30 s, every 30 s, every 90 s rejected; with two readers, a declared on-grid phase plus an undeclared one accepted, two disagreeing phases or two intervals rejected),a_window_equal_to_its_interval_is_kept_at_ingestion_time(PromQL KLL 5m every 300 s over 3 evaluations, sum over 20 s every 20 s over 4, on the epoch-anchored stand-in runtime, equal to the query-time reference),a_window_equal_to_its_interval_at_an_offset_phase_answers_as_reference(phase 5 s, 4 evaluations; before this PR it answered 150105 instead of 152535),a_window_equal_to_its_interval_is_built_at_ingestion_time_from_table_rows(SQL distinct count and exact row count, 5 minutes every 300 s, from table rows).MatAtIngestionoption, so the materialization assignments of a component can grow by up to 2^n for n such builds. Measured on W-RollupHeavy 8 users: without raw data, the 26 formerly unplannable components check 192 to 84,240 candidates in 0.27 s to 71 s; with raw data retained, the 28 components that planned before check 23 to 65 % more candidates (52,014 to 67,902; 19,205 to 23,741; 54,792 to 90,267; the 15-minute L5 components unchanged at 264,612) and take 20 to 70 % longer (at most 139 s), with no new timeout and the same selected cost in every one.m[20s]every 30 s, accepted before) is now pruned; Pass 2'sgcdpanes serve it. Not changed: Pass 2 still offers no one-pane chain, so a 5m@300s query does not join a shared 1-minute pane chain of a 15-minute query; it gets its own whole-window build (see the plan above).Before this PR
A window equal to its interval with no declared phase was kept only at query time, so without raw data the workload had no plan; a whole-window build at a phase off its window's grid was accepted and read the wrong window at runtime.
After this PR
Such a query is one tumbling window maintained at ingestion time and plans without raw data, in PromQL and SQL; a phase off the grid falls back to panes on the grid.
Verification
527d93f4) and pass here: the 3 planner integration tests above (NoValidCandidatex2 and the wrong offset-phase answer on main) and the updated unit test.cargo test -p asap-planner -p asap-integration-tests -p asap-devtools -p asap-plan-selection: all pass (the new PromQL quantile test first ran without an accuracy target, where an exact quantile has no summary to keep; it now asks for rank error 0.01);cargo nextest run -p asap-physical-asap-optimization: 18 passed. The committed dag-viewer examples are unchanged (stage_pipelinetests pass).cargo fmt --all -- --check;cargo clippy -p asap-physical-asap-optimization -p asap-planner --all-targets --all-features --locked -- -D warnings: clean.stage_pipelineon a 56-core server, the 26 components that had no plan:527d93f4Costs of the new plans: 5.3e-07 to 9.6e-06 per second.
With raw data retained, all 34 L4 and L5 components, both binaries on the same server, 12 at a time:
527d93f4The 6 timeouts (four L4 and two L5 distinct-count components) are limited by search size, before and after.
Closes #893
Closes #828