Repository navigation
feat: charge a shared ingestion node once per refresh window (#1151) - #1154
Merged
Merged
Conversation
5 tasks
zzylol
force-pushed
the
feat/1151-refresh-groups
branch
from
October 31, 2026 18:58
5a156af to
7e87761
Compare
zzylol
force-pushed
the
feat/1151-refresh-cost
branch
from
October 31, 2026 19:35
aed2c6f to
3476adf
Compare
zzylol
force-pushed
the
feat/1151-refresh-groups
branch
from
October 31, 2026 19:37
7e87761 to
01d4b49
Compare
zzylol
changed the base branch from
feat/1151-refresh-groups
to
feat/1095-stage3
October 31, 2026 19:37
zzylol
force-pushed
the
feat/1151-refresh-cost
branch
from
October 31, 2026 19:37
3476adf to
9621512
Compare
This was referenced Oct 31, 2026
zzylol
added a commit
that referenced
this pull request
Nov 2, 2026
…ge (#1151, lands #1152–#1154 on main) (#1182) * docs: refresh the ingestion-time views of one refresh window in one run (#1151) (#1152) The deployment contract refreshes the ingestion-time views of one refresh window in one run per window end; the run's roots are the group's views whose state for that window is not yet stored. Part of #1151. * feat: derive ingestion refresh groups and refresh each group in one run (#1151) (#1153) RefreshWindow, PhysicalASAPDAG::refresh_window and ingestion_refresh_groups derive which ingestion-time views share one run; the install compile check and the stand-in runtimes refresh per group, and a counting-store test shows one raw read per window per group. No cost change. Part of #1151. * feat: charge a shared ingestion node once per refresh window (#1151) (#1154) asap_cost::execution_demand::computations counts a NotMaterialized ingestion-time node once per refresh window of its MatAtIngestion consumers instead of once per consumer, and the old Stage 3 engine (general_schedule, DirectCostProfile::selected_cost, direct_cost::encode) charges it the same way. Snapshots unchanged. Closes #1151 except the backend follow-up. * fix: use the tumbling-window names (#1163) in the refresh-window code rebased onto main The refresh-window commits (#1152-#1154) were written before #1163 renamed PaneChain to SummaryTumblingWindowChain and pane_width_ms / pane_count / newest_panes to tumbling_window_width_ms / tumbling_window_count / newest_tumbling_windows; rename their uses so they build on main.
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
Part of #1151, stacked on #1153 (refresh groups) and #1152 (contract). The runtime now refreshes the views of one refresh window in one ingestion run, so a
Scanunder a KLL chain and a DDSketch chain of 10 s panes is computed once per pane.asap_cost::execution_demand::computationsstill charges it once perMatAtIngestionconsumer:(multiplier 2, 2.0/s), detailscan … x 2.0000/s. The old Stage 3 engine has its own copy of the per-consumer rule. Its certified bound (validate_observations) rejects an observation whose evaluated cost is below the modeled bound, so the rule and the engine must change together, in one PR.What
asap-cost): incomputations, theMatAtIngestionconsumers of aNotMaterializednode that share one refresh window (PhysicalASAPDAG::refresh_window) count as one consumer: +1 to the multiplier and +1.0/s to the rate.plan_cost/node_usagetake the new(multiplier, per_second)unchanged, so ingestion CPU, scanned bytes, peak working memory and ingestion cores follow.asap-plan-selection):NodeCostProfilegetsrefresh_window, set for a pane chain maintained at ingestion.DirectCostProfile::selected_cost, the modeled cost.direct_cost::encode.general_schedule::encode.crates/plan-selection/src/lib.rsstates the rule. So doNodeCost::multiplierandResourceUsage::peak_memory_bytesincrates/cost/src/lib.rs, and, moved here from docs: refresh the ingestion-time views of one refresh window in one run (#1151) #1152, the "Refresh groups" paragraph ofphysical-planning-and-deployment.md§4 and thematerialized_view.rsmodule doc, which say how Stage 3 prices the grouping.RefreshWindow::of_windowfromasap-types, so a width-0 chain gets no window instead of a bad one.planner_layering_common::computationsfollows the rule.RefreshWindowderivesPartialOrd, Ord, so the engine keys its variables in aBTreeMapand creates them in a deterministic order.How
computationscomputesdag.refresh_windows(MatAtIngestion nodes)once. While it adds up each node's consumers, it skips aMatAtIngestionconsumer whose refresh window has already been counted for that node.In the engine, a boundary maintained at ingestion now carries its refresh window. Its uses of an unmaterialized node go into
runs[(node, window)]instead of one demand each. Each entry becomes one variablerun ∈ [0, 1]withrun ≥ usefor every use, andruntakes the place of those demands in the objective.The direct profile admits no
TimeShiftat ingestion, so a chain's newest pane ends at the evaluation time, and its refresh window is(pane_width_ms, 0).Key code interfaces added / modified
crates/cost/src/execution_demand.rscrates/types/src/ir/materialized_view.rscrates/plan-selection/src/search/milp/direct_cost.rs(crate-internal; the other fields carry no doc comments in the code)crates/integration-tests/tests/planner_layering_common/mod.rsUsage (
crates/cost/tests/ingestion_refresh_runs.rs, run at this commit):Major algorithm changes
Ingestion demand of a shared stateless node
Scanunder 10 s KLL and 10 s DDSketch chains,computationsgave(2, 2.0), so Stage 3 priced the Scan twice. The runtime (feat: derive ingestion refresh groups and refresh each group in one run (#1151) #1153) runs it once per pane. The old engine modeled the same double charge.computationsgave each unmaterialized node+1, +rateper consumer. The engine pushed one demandboth(reached, used)per consumer, at rate 1.0 for an ingestion consumer.MatAtIngestionconsumers byrefresh_window.+1, +1.0. Every other consumer adds as before.y ≥ use_cfor each consumercof that window, chargedexecution_cost × 1.0 × y.ydown tomax_c use_c, the OR of the uses, so the objective equalsselected_cost.selected_costapplies the same rule ascomputations.direct_profile::tests::chains_of_one_refresh_window_share_their_scan_in_the_direct_profilechecks modeled = evaluator (within 1e-9) and the Scan multiplier on three cases: equal schedules with 10 s panes (multiplier 1), mixed schedules every 20 s and every 40 s with 20 s panes (1, general encoder), and 10 s + 60 s panes (2).lower_bounds.rsprices with(1, 1.0), so it is still a lower bound.refresh_windowsis O(nodes + edges), once percomputationscall. The engine adds at most one variable per (unmaterialized ingestion candidate, refresh window) and one constraint per use, and drops the same number of per-consumer demand terms. Ingestion groups are only Scan, Filter, Project and TimeRange.Snapshot diffs
Regenerated with
UPDATE_SNAPSHOTS=1 cargo test --release -p asap-devtools --test corpus_snapshots -- --ignored. Bothcorpus_plans_match_snapshotsandsketch_plans_match_snapshotspassed, and no snapshot file changed: no selected plan, cost or status differs. This matches #1151's expectation. No committed plan has a node with multiplier > 1 at ingestion: the two 60 s chains ofo11y_bench/q19andq25read two differentScans.Before this PR
Stage 3 charged a stateless ingestion node once per ingestion consumer. That was right for per-view refreshes, and it over-charged the one run per refresh group that #1152/#1153 introduce.
After this PR
Stage 3 charges such a node once per refresh window of the views above it, in
asap-costand in the old Stage 3 engine alike. Equal-width chains over one source become cheaper together, so the cost of one chain's pane width now depends on the other chains' widths.Verification
asap-costwithout the rule:a_scan_under_two_chains_of_one_pane_width_is_computed_oncefails,left: (2, 2.0),right: (1, 1.0).refresh_window): the direct-profile test fails, "direct 0.051930…, evaluator 0.051670…". The modeled cost is above the evaluated one, whichvalidate_observationswould reject in production.refresh_windowset, old rule): the same test fails, "direct 0.051670…, evaluator 0.051930…". The engine models the shared run and the evaluator does not.cargo test -p asap-cost: all passed, 7 of them new iningestion_refresh_runs:TimeShift(10 s)→ (1, 1.0) for the shift and for theScan.cargo test -p asap-plan-selection --lib: 285 passed.structural_seed_timeout_preserves_validated_incumbent(a 1 s wall-clock test) failed once under machine load and passed on rerun.cargo test -p asap-integration-tests --test integration -- planner_layering range_evaluation sql_to_physical reuse_rules materialization_e2e: 103 passed, 20 ignored.cargo test -p asap-devtools --test devtools stage_pipeline: 48 passed.cargo fmt --all --checkandcargo clippy -p asap-types -p asap-cost -p asap-plan-selection -p asap-integration-tests --all-targets -- -D warnings: clean.Remaining for #1151, tracked separately: the #1095 text (O3(b), rows (2)–(4), G5) and the backend follow-up in its repository.
Note: on the issue's first example (1 s samples), the Planner still picks 1 s panes for the DDSketch chain and 10 s panes for the KLL chain under the new rule. The saved scan does not outweigh the DDSketch pane-width choice there, so that plan has two refresh groups.
🤖 Generated with Claude Code