Repository navigation
refactor(ir, planner): InstallablePlan holds a versioned DAG and query texts; StoredOutput becomes MaterializedView - #991
Merged
Conversation
…y texts; StoredOutput becomes MaterializedView
The plan file is now InstallablePlan { wire_version, dag, query_texts }.
Each materialized view's retention_ms is a field of its DAG node, and
everything else a runtime needs is derived from the DAG in asap-types:
PhysicalASAPDAG::materialized_views() (refresh mode, coverage, inputs,
retention) and raw_data_retention() (per raw-data source, the longest
query-time scan_window lookback, the quantity Stage 3 prices).
- Remove PhysicalASAPDAGDocument and PHYSICAL_ASAP_DAG_WIRE_VERSION; add
INSTALLABLE_PLAN_WIRE_VERSION (11), covering the whole plan file.
- Remove the derived fields queries, stored_outputs and
query_time_raw_windows, and RawDataSampleWindow (replaced by
RawDataWindow, which names the Source a view scans).
- Rename StoredOutput, StoredOutputBuild::{IngestionRun, QueryRun},
StoredOutputInputs::stored_outputs and StoredCoverage to
MaterializedView, MaterializedViewRefresh::{AtIngestion, OnFirstRead},
MaterializedViewInputs::materialized_views and ViewCoverage.
- PhysicalASAPDAG::validate rejects retention_ms on a non-view node.
- plan_installable and installable_plan_of return Stage 3's report beside
the plan; InstallablePlan::validate checks only non-derived facts.
- query_time_reference moves to asap_planner::test_support (feature
test-support).
- Add tests: raw_data_retention of rate(m[5m]) + rate(m[5m] offset 1h)
is 65m and equals Stage 3's priced lookback; materialized_views()
equals the stored_outputs main listed, for plans captured from main.
The stage_pipeline document's stage3_plan_selection.stored_outputs is now
materialized_views (materialized_views_error), each entry the planner's
MaterializedView: refresh (at_ingestion, on_first_read) and inputs
{ raw_data_windows with their source, materialized_views }. The viewer,
its tests, README, the user guide, the committed example documents and
the evaluation scripts read the new shape.
…lan-file read; derivation deadline - Each query-time Scan of an installed DAG carries retention_ms: its readers' scan_window lookback plus their longest range-evaluation span, at least the retention of every view refreshed on first read from it (None when unbounded). raw_data_retention() is the per-source max and checks it covers each window, so a range-evaluated or kept-pane plan keeps raw data for its oldest step (65 min, not 5, for a 5m window over a 1h range). set_materialized_view_retention becomes set_retention. - PhysicalASAPDAG::validate allows retention_ms on views and query-time scans (UnexpectedRetention elsewhere). - InstallablePlan::from_json reads wire_version first; an older file is UnsupportedWireVersion(None | Some(v)), tested with a v10 plan file. - set_retention takes the compile check's deadline. - scan_window indexes the DAG once per call (same result). - Unit tests for ViewWithoutRetention, ViewRetentionTooShort, retention propagation and the deadline.
zzylol
force-pushed
the
refactor/970-installable-plan
branch
from
October 28, 2026 05:33
a140089 to
024a570
Compare
This was referenced Oct 28, 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.
Closes #970. Part of #968 (PR 6 of 6).
Why
The plan file a runtime installs kept two copies of most facts: the DAG, and fields derived from it (
queries[i].root_node_id,stored_outputs,query_time_raw_windows).InstallablePlan::validatemostly re-derived those fields and compared them. The file also carried Stage 3'sselectionreport, which a runtime never reads. The version sat one level below the file it versioned (plan.dag.schema_version,plan.dag.dag). And a runtime could not tell how long to keep raw data, although Stage 3 prices exactly that.What
InstallablePlanis{ wire_version, dag, query_texts }.INSTALLABLE_PLAN_WIRE_VERSION = 11versions the whole file.PhysicalASAPDAGDocumentandPHYSICAL_ASAP_DAG_WIRE_VERSIONare removed.PhysicalASAPDAGNodegainsretention_ms, set at installation on two kinds of node:MatAtIngestionorMatAtQueryKeptAcrossEvaluations);Scan, holding how long its source's rows are kept, orNonewhen it reads them unbounded.PhysicalASAPDAG::validaterejects it on any other node.materialized_views()andraw_data_retention()are derived from the DAG and those node retentions. They live inasap-types(asap_types::ir::materialized_view), so a runtime can call them without the planner crate.Renames:
StoredOutput→MaterializedView,StoredOutputBuild::{IngestionRun, QueryRun}→MaterializedViewRefresh::{AtIngestion, OnFirstRead},StoredOutputInputs→MaterializedViewInputs { raw_data_windows, materialized_views },StoredCoverage→ViewCoverage.RawDataSampleWindowbecomesRawDataWindow { source, end_offset_ms, width_ms }. It names theSourcethe view scans instead of two node ids.plan_installableandinstallable_plan_ofreturn(InstallablePlan, Option<SelectedPlanReport>).query_time_referencemoves toasap_planner::test_support(featuretest-support).InstallablePlan::from_jsonreadswire_versionfirst. An older file, such as a v10 file that has nowire_version, is refused asUnsupportedWireVersion, not as a missing field.The DAG viewer document's
stored_outputsbecomesmaterialized_views, with the new entry shape.How
PhysicalASAPDAG::set_retention(pane_retention_ms_by_node, reader_span_ms_by_node, deadline)sets two kinds of retention on the expanded DAG.installable_plan_ofcalls it, then compiles each view's refresh with the executor, as before.runtime_inputs_ofused to do. That is Stage 3's pane retention, or the reach plus the readers' range span, then propagated to the views each one reads.scan_window(scan).lookback_ms()plus the readers' longest range-evaluation span, and at least the retention of every view refreshed on first read from that scan. It isNonewhen the window is unbounded.DeadlineExceededwhen the compile check's deadline passes.PhysicalASAPDAG::materialized_views()makes the same DAG walk thatruntime_inputs_ofmade: the inputs, the shape check of every raw-data window, the ingestion offsets check and the coverage. It takes retention from the nodes and checks that each view's retention covers its reach.PhysicalASAPDAG::raw_data_retention()takes, perSourceread by a query-timeScan, the max of those scans'retention_ms, orNonewhen one is unbounded. It checks that each bounded scan has a retention covering its window.InstallablePlan::validatechecks only non-derived facts: the wire version, one text per root with no scalar root, and that view and scan retention cover their reach. The DAG's validity is checked insidematerialized_views().PhysicalASAPDAG::scan_windownow indexes the DAG once per call: O(nodes + edges) instead of a linear search per edge. Its result is unchanged.UnsupportedRawDataWindow,ViewReadsUnwindowedSource,ViewWithoutCoverage,IngestionBuildReadsWindowsOfDifferentOffsets,ViewWithoutRetention,ViewRetentionTooShort,ScanWithoutRetention,ScanRetentionTooShort,DeadlineExceeded) move toInstallableDagErrorinasap-types.InstallablePlanError::Uninstallablewraps them, and Stage 3's compile check still rejects a candidate for any of them.Key code interfaces added / modified
crates/types/src/ir/materialized_view.rs(new module)crates/types/src/ir/physical_export.rs,crates/types/src/physical/materialization.rscrates/planner/src/installable.rs,crates/planner/src/lib.rsSelectedPlanReport,PredictedUsage,PredictedNodeUsage,PredictedQueryUsageandPlannerExecutorCompileCheckare unchanged. The planner re-exportsMaterializedView,MaterializedViewInputs,MaterializedViewRefresh,RawDataRetention,RawDataWindow,ViewCoverageandInstallableDagError. It no longer re-exportsPhysicalASAPDAGDocument,RawDataSampleWindow,InstalledQuery,StoredOutput*orStoredCoverage.Plan file and viewer document formats
InstallablePlanJSON): top-level keys arewire_version,dagandquery_texts. Indag.nodes[], a view node and a bounded query-timeScanhave"retention_ms": <u64>; other nodes omit it.tools/dag-viewer/stages.jsdocument contract:stage3_plan_selection.stored_outputs/stored_outputs_error→materialized_views/materialized_views_error. Each entry is aMaterializedView(refresh:at_ingestion|on_first_read;inputs:raw_data_windows[{source, end_offset_ms, width_ms}],materialized_views) plusstage2_node_id,installed_positionandinstalled_count.Usage
Compiled and run as a test in
crates/integration-testsat this head (not committed; the committed equivalents arecrates/integration-tests/tests/materialized_views.rsandruntime_handoff.rs):With raw data kept (the default capabilities),
rate(m[5m])plusrate(m[5m] offset 1h)plan at query time.plan.raw_data_retention()is then[RawDataRetention { source: TimeSeries { metric: "m" }, lookback_ms: Some(3_900_000) }], which is 65 minutes and equals Stage 3's priced lookback (raw_data_retention_is_the_lookback_stage3_prices).The same query,
max_over_time(m[5m]), range-evaluated over the last hour at query time also keeps 65 minutes ofm: the 5-minute window plus the hour of steps (a_range_evaluated_query_time_scan_keeps_its_window_plus_the_span).Major algorithm changes
Raw-data retention (new).
scan_window, which misses range evaluation. Formax_over_time(m[5m])over the last hour with query-time results kept, the panes are kept 3,900,000 ms, but that version saidm: 300_000. Refreshing a pane for the oldest step reads 65 minutes back.set_retentiongives each query-timeScanthe following, andraw_data_retentiontakes the per-source max:scan_window(scan).lookback_ms();reader_span_ms_by_node);retention_msof each view refreshed on first read from the scan;Nonewhen the window is unbounded.raw_data_retention). Its oldest read is a range query's oldest step, or the oldest refresh of a view refreshed on first read. Tests:query_time_results_kept_across_evaluations_are_built_in_query_runs(now 65 min),a_range_evaluated_query_time_scan_keeps_its_window_plus_the_span,raw_data_retention_is_the_lookback_stage3_prices.scan_windowis now indexed once per call.Retention of views, coverage, view inputs and the compile check compute what they did before; they are moved into
asap-typesand read from the DAG.Before this PR
validaterecomputedqueries,stored_outputsandquery_time_raw_windowsand rejected a mismatch. A runtime had no raw-data retention to read.After this PR
Every fact has one source. A runtime that depends only on
asap-typesderives the views and the raw-data retention from the plan file'sdag.Verification
main: plans captured onmain(f06a555) are incrates/integration-tests/tests/materialized_view_fixtures/stored_outputs_on_main.json. There are eight: four repeating PromQL workloads without raw data, and fourruntime_installforced plans covering rollups read by views and a kept sliding-window merge refreshed on first read. Each holds the plan file's DAG and itsstored_outputs.materialized_views_equal_the_stored_outputs_main_listedchecks thatmaterialized_views()of each DAG, with the captured retention on the nodes, equalsstored_outputsfield for field after the renames. It passes.mainforp99-60m,p99-p50-60mandoffset-chain. Therollupcapture differs, but the main capture was a release build and the comparison a debug build. A debug build ofmainselects the same candidate (seed-joint-plan-2, 0.08500 cost/s) as this branch, so the difference is not from this PR.raw_data_retention_is_the_lookback_stage3_prices:m: Some(3_900_000)(65 min), equal to the per-source max ofscan_window(..).lookback_ms()over the query-time scans of Stage 3's selected DAG. Without raw data kept, it is empty.a_range_evaluated_query_time_scan_keeps_its_window_plus_the_span(65 min against Stage 3's 5) shows.crates/types/tests/materialized_view.rscoversViewWithoutRetention,ViewRetentionTooShort, retention propagation from a view refreshed on first read to its inputs (panes kept 2 min + 1 h), and the derivation deadline.a_plan_file_of_an_older_wire_version_is_refused_by_version: a v10 plan file is refused byfrom_jsonasUnsupportedWireVersion(None). The file ismaterialized_view_fixtures/plan_wire_version_10.json, assembled from the DAG andstored_outputsof a plan written onmain, in the v10 layout{dag: {schema_version: 10, dag}, queries, stored_outputs, query_time_raw_windows, selection}. A newer version is refused asSome(v), and a current file round-trips.main249ebd2), withLIBCLANG_PATHset:cargo test -p asap-types --test physical_export --test materialized_view: 18 / 4 passed.cargo test -p asap-planner --lib: 6 passed.cargo test -p asap-planner --test runtime_install --test per_sample_updater --test columnar_batches --test sql_per_group_topk --test sql_table_scans: 41 / 2 / 1 / 6 / 11 passed.cargo test -p asap-integration-tests --test materialization_e2e --test per_series_exact_execution --test runtime_handoff --test tumbling_pane_merge --test kll_pane_execution --test materialized_views: 4 (1 ignored) / 8 / 8 / 13 (1 ignored) / 2 / 4 passed.cargo test -p asap-plan-selection --lib raw_data: 7 passed.cargo test -p asap-devtools --test viewer_contract: 2 passed.cargo test -p asap-devtools --test stage_pipeline: 45 passed, 2 failed. Each failure gives the same value when the same test runs onmain, so neither is from this PR:workload_file_with_a_range_evaluation_is_planned: cost 719154.73, also onmainf06a555.scans_projecting_different_labels_are_shared: cost 0.21003, also onmain249ebd2.tools/dag-viewer:python3 -m unittest test_viewer(74 OK),python3 -m unittest test_server(28 OK),node --test stages.test.mjs(14 pass).cargo fmt --all --check: clean.cargo clippy -p asap-types -p asap-planner -p asap-devtools -p asap-integration-tests -p asap-executor -p asap-plan-selection --all-targets -- -D warnings: clean.cargo check --workspace --all-targets --all-features: clean.producer_reuse_rules_produce_validated_shared_plans, which is known to fail on main (from test(optimizer): read the reuse and sharing tests from the memo, not the whole-workload Pass 2 #983/fix(optimizer): an exact rollup reads the finer query's own result #984).Limitations and follow-ups
InstallablePlanstays in the planner crate, becausecheck_compilesneeds the executor and the planning errors live there. The derivation and its types are inasap-types, so a runtime that depends only onasap-typescan callPhysicalASAPDAG::materialized_views()andraw_data_retention()on the plan file'sdag.materialized_views()andraw_data_retention()return aResult, where the issue sketch returns aVec. A plan file read from disk may be invalid: a missing or too-shortretention_ms, or a raw window of an unsupported shape.set_retentionis public, because the planner crate calls it. A runtime does not need it.f46cbf6c, must move to the new plan-file format (wire version 11).docs/design_docs/physical-planning-and-deployment.mdstill namesstored_outputs. docs(design): physical DAG to deployment interface (raw data and materialized view slots, DataStore) #972 owns that document.