Skip to content

feat(query): build stored outputs built in a QueryRun on first use - #824

Merged
zzylol merged 1 commit into
mainfrom
feat/kept-query-time-results
Oct 21, 2026
Merged

zzylol merged 1 commit into
mainfrom
feat/kept-query-time-results

Conversation

@zzylol

@zzylol zzylol commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Base: main (#821 merged). Next: #839 (the #814 event-time barrier port).

Part of #837: stored outputs built in a QueryRun (MatAtQueryKeptAcrossEvaluations) are now built, stored in the SDS and reused. Closes the gap #838 left: a plan with query-run outputs installed, but nothing built them.

Why

The Planner can choose to compute a summary at query time and keep it across evaluations (MatAtQueryKeptAcrossEvaluations; #509 Example 4's B3). The backend declared it could not keep query-time results, so the Planner never selected such a plan. When a deployment has raw data in Prometheus and evaluates often, that plan is the cheapest.

What

  • Stored outputs built in a QueryRun: a stored output with built_at: query_run is bound like an ingestion-built one, to its definition's summary ending at T − offset, with these rules:
    • First use: if the SDS has no such summary state, the query engine fetches the coverage's raw samples and builds it as at the coverage's end.
    • Store once closed: it is stored in the SDS store, under the same SummaryInstanceKey as stored outputs built in an IngestionRun, only once its pane is closed. Closed means its end is at or before the watermark when the evaluation chose its fetch: bound_raw_sample_windows returns that snapshot (IngestionProgress) with the ranges, and evaluate decides with it. A summary state built from an earlier, partial fetch is therefore never stored, even if ingestion closed the pane meanwhile. One read while its pane is still open is built for that evaluation only.
    • Reuse: later evaluations read the stored summary and fetch nothing for it.
    • Retention: evicted by the definition's largest retention_ms plus one pane, measured from the newest closed pane at or before the watermark, the same as stored outputs built in an IngestionRun. A far-future evaluation therefore neither stops storing nor evicts.
  • One fetch per definition: for each QueryRun definition, one bound raw-sample window covers all its missing summaries, with its Scan label matchers.
  • Counters: /api/v1/status/asap reports query_run counters: built, stored, stored_while_metric_behind and reused. Closure is judged by the global watermark, so a metric read by QueryRun outputs that lags behind other metrics can have a summary state stored before all its raw samples arrived. stored_while_metric_behind counts those: the summary state's own metric's newest sample had not reached its end. A per-metric watermark is the follow-up.
  • Watermark: samples of a metric only stored outputs built in a QueryRun read are not buffered, but they move the watermark (samples_query_run_only), with the same future bound and wall-clock cap as routed samples.
  • Install: a QueryRun definition whose selector does not build no longer fails the install. Its queries are forwarded, the same as on the query side.
  • Shared build: SummaryBuild compiles a definition's build and runs it over one coverage's samples. The precompute and query engines share it.
  • Capability: the backend now declares query_time_results_retained: true, and feat(query): answer planned instant queries by running the plan's roots #821's startup warning is removed. examples/planner-layering-example4-b3.json is a workload (ten series sampled every millisecond, p99 over 5 minutes every minute) for which the Planner selects B3.

Key code interfaces added / modified

// data_plane/src/precompute.rs
/// How one definition's summaries are built from raw samples.
pub struct SummaryBuild { pub definition: SummaryDefinition, /* private */ }
impl SummaryBuild {
    pub fn new(dag: &PhysicalASAPDAG, definition: &SummaryDefinition) -> Result<Self, PrecomputeError>;
    pub fn metric(&self) -> &str;
    pub fn run(&self, end_ms: i64, samples: &[RawSample], scope: Scope) -> Result<Batch, PrecomputeError>;
}
pub struct PrecomputeStats {
    // ...
    /// Samples of a metric only stored outputs built in a `QueryRun` read.
    pub samples_query_run_only: u64,
}

// data_plane/src/sds.rs
pub struct PaneBinding {
    pub offset_ms: i64,
    /// The output's own `TimeRange`, which its coverage's raw samples feed
    /// when a stored output built in a `QueryRun` is built for it.
    pub raw_input: u64,
}

// data_plane/src/query.rs
pub struct QueryRunStats { pub built: u64, pub stored: u64, pub stored_while_metric_behind: u64, pub reused: u64 }
/// What ingestion had seen when an evaluation chose its fetch.
pub struct IngestionProgress {
    pub watermark_ms: Option<i64>,
    /// Newest sample of each metric stored outputs built in a `QueryRun` read.
    pub newest_sample_ms: BTreeMap<String, i64>,
}
pub struct Prepared { pub root: usize, pub windows: Vec<BoundRawSampleWindow>, pub progress: IngestionProgress }
pub struct QueryEngine { pub query_run_stats: QueryRunStats, /* private */ }
impl QueryEngine {
    pub fn bound_raw_sample_windows(&self, query: &str, time_ms: i64, store: &SummaryStore<Batch>,
        progress: IngestionProgress) -> Result<Prepared, Unanswered>;
    /// Stores stored outputs built in a `QueryRun` in `store` if closed in `progress`.
    pub fn evaluate(&mut self, root: usize, time_ms: i64, store: &mut SummaryStore<Batch>,
        progress: &IngestionProgress, raw: &BTreeMap<u64, Vec<RawSample>>)
        -> Result<Vec<ResultSample>, Unanswered>;
}
// data_plane/src/precompute.rs
impl Precompute { pub fn progress(&self) -> IngestionProgress; }

// control_plane/src/lib.rs: backend_capabilities().query_time_results_retained == true

Before this PR

The backend declared query_time_results_retained: false. A plan with stored outputs built in a QueryRun was never selected, and a query reading one would have been forwarded.

After this PR

Example 4-B3: the first evaluation at 10 min builds five 1-minute summaries. The first evaluation makes one fetch for (5, 10 min]. The evaluation at 11 min fetches only (10, 11 min], builds one summary, reuses four, and answers like the Planner's query-time reference.

Verification

  • data_plane/tests/query_run.rs (5 tests), all against InstallablePlan::query_time_reference:
    • answers match the reference, with build, store and reuse counts and one fetch for five panes;
    • eviction by the plan's retention measured from the watermark. A far-future evaluation stores nothing and evicts nothing, and storing resumes afterwards; the old version of this test locked in the wrong behavior;
    • summary states are stored only once their pane is closed, including a late sample;
    • stores_by_the_watermark_the_fetch_was_chosen_at: through the engine, ingestion closes the coverage between bound_raw_sample_windows and evaluate. The summary state ending at 10 min is not stored, and the next evaluation fetches it again. This is the review's repro;
    • counts_outputs_stored_while_their_metric_is_behind.
  • data_plane/tests/sds.rs: query_run_outputs_share_one_definition.
  • data_plane/tests/precompute.rs: query_run_metrics_move_the_watermark, query_run_metrics_are_bounded_like_routed_samples.
  • control_plane/tests/plan_workload.rs: selects_stored_outputs_built_in_a_query_run.
  • cargo test --workspace --locked passes.

@zzylol zzylol added enhancement New feature or request area: data plane Runtime ingestion, storage, and query-serving data paths area: query engine PromQL lowering, execution, result shaping, and fallback behavior integration: ASAPPlanner ASAPPlanner API, IR, candidate, and selected-plan integration test: end-to-end Process-boundary, differential, conformance, and full-stack tests labels Oct 18, 2026
@zzylol
zzylol force-pushed the feat/kept-query-time-results branch from e0beb60 to 8827771 Compare October 20, 2026 20:06
@zzylol zzylol changed the title feat(query): keep query-time panes across evaluations feat(query): build query-run summaries on first use and keep them once closed Oct 20, 2026
@zzylol
zzylol changed the base branch from test/e2e-examples to feat/query-engine October 20, 2026 20:06
@zzylol
zzylol force-pushed the feat/query-engine branch from 2142441 to 1bdf7e9 Compare October 20, 2026 23:26
@zzylol
zzylol force-pushed the feat/kept-query-time-results branch from 8827771 to d1feb90 Compare October 20, 2026 23:26
@zzylol
zzylol force-pushed the feat/query-engine branch from 1bdf7e9 to afa5bc6 Compare October 20, 2026 23:46
@zzylol
zzylol force-pushed the feat/kept-query-time-results branch from d1feb90 to 9bfa31f Compare October 20, 2026 23:46
@zzylol
zzylol marked this pull request as ready for review October 20, 2026 23:46
@zzylol
zzylol force-pushed the feat/kept-query-time-results branch from 9bfa31f to 185f7db Compare October 20, 2026 23:53
@zzylol zzylol changed the title feat(query): build query-run summaries on first use and keep them once closed feat(query): build stored outputs built in a QueryRun on first use Oct 20, 2026
@zzylol
zzylol force-pushed the feat/kept-query-time-results branch from 185f7db to a3fa8e0 Compare October 20, 2026 23:56
@zzylol
zzylol changed the base branch from feat/query-engine to main October 21, 2026 00:07
A stored output with StoredOutputBuild::QueryRun (materialization
MatAtQueryKeptAcrossEvaluations, #509 Example 4's B3) is now executed. The
query engine builds its summary state from the pane's raw samples the
first time an evaluation needs it, and stores it in the SDS under the same
SummaryInstanceKey as stored outputs built in an IngestionRun once the
pane is closed. Later evaluations reuse it.

- Closure is judged by the ingestion progress snapshotted when the
  evaluation chose its fetch (bound_raw_sample_windows returns it with the windows and
  evaluate takes it back), not when the evaluation runs, so a summary
  state built from an earlier, partial fetch is never stored.
- Eviction is by the plan's retention_ms plus one pane, measured from the
  newest closed pane at or before the watermark, as for IngestionRun
  outputs. A far-future evaluation no longer stops storing.
- One fetch per QueryRun definition covers all its missing panes.
- QueryRunStats (built, stored, stored_while_metric_behind, reused) is on
  /api/v1/status/asap under "query_run". stored_while_metric_behind
  counts summary states stored while their own metric's newest sample had
  not reached their end: the watermark is global.
- A QueryRun definition whose selector does not build no longer fails the
  install: its queries are forwarded.

SummaryBuild, shared by the precompute and query engines, compiles a
definition's build and runs it over one pane's samples. Samples of a
metric only QueryRun stored outputs read move the watermark (counted as
samples_query_run_only) without being buffered.

The backend now declares query_time_results_retained: true, and the
startup warning about unbuilt QueryRun outputs is gone.
examples/planner-layering-example4-b3.json is a workload for which the
Planner selects B3 under the default cost model.
@zzylol
zzylol force-pushed the feat/kept-query-time-results branch from a3fa8e0 to 5780d83 Compare October 21, 2026 00:09
@zzylol
zzylol merged commit 11b67c1 into main Oct 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: data plane Runtime ingestion, storage, and query-serving data paths area: query engine PromQL lowering, execution, result shaping, and fallback behavior enhancement New feature or request integration: ASAPPlanner ASAPPlanner API, IR, candidate, and selected-plan integration test: end-to-end Process-boundary, differential, conformance, and full-stack tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant