Repository navigation
[Refactor] Time-series TimeRange as a native operator; bind raw data only at Scan; remove the raw-sample selectors #969
Description
Example
rate(http_requests_total{job="api"}[5m]) and http_requests_total{job="api"} (an instant selector with the default 5m lookback).
The DAG has Scan(Source::TimeSeries { metric: "http_requests_total" }, predicate job = "api") under TimeRange(Range, 5m) or TimeRange(Instant, 5m).
Today the executor has no operator for a time-series TimeRange. A runtime computes the window rows outside the DAG and binds them at the TimeRange node (crates/executor/src/samples.rs):
let selector = RawDataSampleSelector::new(&plan.dag.dag, window)?;
// the runtime routes samples whose __name__ == selector.metric() here
let rows = selector.window_batch(window_end_ms, &samples)?; // samples: &[RawDataSample]window_batch does all of the following itself:
- keeps samples in
(end - 5m, end]; - evaluates the
Scan's label matchers; - for an instant selector, keeps each series' latest sample;
- drops Prometheus stale markers, so a series whose latest sample is a stale marker has no row.
A SQL table window goes through TableRowSelector::window_batch, which repeats the table TimeRange filter that the executor already lowers natively (table_time_operator, physical_planner/mod.rs:725).
After this issue, both are bound at the Scan through TableProvider (#966). A time series is a table (ts, value, *labels), and the DAG's own TimeRange gives these answers:
Query at T |
Rows the TimeRange outputs |
|---|---|
[5m] range |
every sample with ts in (T - 5m, T], stale markers dropped |
| instant, 5m lookback | per series, the latest sample in (T - 5m, T], or no row when that sample is a stale marker |
Concrete maintenance problem
- Window and label-matcher semantics have three implementations: the table
TimeRangelowering,TableRowSelector, andRawDataSampleSelector. A fix to the(end - width, end]boundary has to be made in all three. - PromQL semantics (instant lookback, stale markers) live outside the DAG.
PlannerExecutorCompileCheckcuts at theTimeRange, so it never compiles them and cannot reject an unsupported time-series window. samples.rs(615 lines) puts a Prometheus sample model in the executor's public API:RawDataSample { labels, timestamp_ms, value }, routing by__name__, stale markers, andseries_labels_of_output_row.RawDataSampleWindowbinds attime_range_node_id, a node above theScan, so raw data enters the DAG in two places.
Behavior that must remain unchanged
Every PromQL and SQL query returns the same answers, including:
- range-window boundaries
(end - width, end]; - an ordinary
NaNsample is kept, and a stale marker is never a row; - instant-selector lookback, where an instant whose latest sample is stale has no row;
TimeShiftoffsets, where a@anchor is still refused for tables.
Before and after structure
-
TimeRangeover a time-seriesScanlowers to a native operator.- A range window filters
tsand drops stale markers. - An instant window filters the lookback, keeps each series' latest row, and drops it if it is a stale marker.
- Series identity is the label columns, as
series_identitycomputes today.
- A range window filters
-
The table
TimeRangelowering and the time-series one share the window computation, which takes the shifts below theTimeRangeandT. -
One scan-window function, in
asap-types. "Which window of aScanits readers read, back from the evaluation time" is computed once, onPhysicalASAPDAG. For example:fn scan_window(&self, scan: NodeId) -> ScanWindow, whereScanWindowisWithin { end_offset_ms, width_ms }(the rows(T - end_offset - width, T - end_offset], the union over every reader) orUnbounded(a root or non-window reader, or an@anchor). Its callers:- the executor's table-slot window ([Refactor] TableProvider (RawSource renamed) with a ScanRequest, and one BindingDataSources for table slots and materialized-view slots #966) and view window;
- the table and time-series
TimeRangelowering; - Stage 3's
query_time_lookback_ms/windows_read(plan-selection/src/lib.rs:4745/4859). Their lookback becomesend_offset_ms + width_ms; PaneChainreaders before expansion. A chain contributes its whole window, or one pane for a chain inkept_chains, aswindows_readdoes today.
This fixes example 1 of Retained raw data as concrete state groups per lookback: offsets, @ and a retention limit #955. Stage 3 keeps and prices 65 minutes of
mforrate(m[5m] offset 1h)at query time, not 5, because todaywindows_readdrops theTimeShiftoffset ("moves a window without widening it"). The executor already reads(T - 65m, T - 60m].@anchors, theRawRetentioncapability and theraw window(S, H)memo groups stay in Retained raw data as concrete state groups per lookback: offsets, @ and a retention limit #955. -
Raw data is bound only at
Scan, throughBindingDataSources.tables. -
Removed:
RawDataSampleSelector,TableRowSelector,RawDataSample,series_identity's public export, andseries_labels_of_output_row. Answer labels are read from the output schema's label columns. -
RawDataSampleWindowno longer names a binding node. WhetherInstallablePlanstill needs it is decided in the next PR of [Tracking] Physical DAG → deployment boundary: InstallablePlan, input slots, TableProvider, BindingDataSources #968.
Why the proposed structure is minimally complex
One window implementation replaces three. PromQL semantics move into the operator that the compile check already compiles. No new interface is added: a runtime that buffers Prometheus samples implements a TimeSeries TableProvider over its buffer.
Existing unit and end-to-end protection
- Selector tests to rewrite against a provider bound at the
Scan, asserting the same rows:crates/planner/tests/{runtime_install,per_sample_updater,columnar_batches,sql_per_group_topk,sql_table_scans}.rs;crates/integration-tests/tests/per_series_exact_execution.rs;crates/executor/tests/thread_safety.rs.
- Add tests:
- an instant selector whose latest sample is a stale marker (no row);
- a range window holding an ordinary
NaN(kept); - a time-series window the executor cannot run, which the compile check now rejects;
- failing first: Stage 3 prices
rate(m[5m] offset 1h)at query time with a 65-minute raw lookback (Retained raw data as concrete state groups per lookback: offsets, @ and a retention limit #955 example 1); scan_windowon aScanread by[5m]and[1h] offset 10mis(T - 70m, T]; with an@anchor it isUnbounded.
- Stage 3 regressions: the
raw_data_retainedtests inplan-selection,stored_data_without_raw_data_retained_is_bad_input, andmaterialization_e2e. Plans without offsets keep the same costs.
Compatibility or migration impact
This is a public API change in asap_executor: the samples items are removed. Stage 3 raw-data costs change only for plans that read a shifted window at query time (the #955 fix). ASAPQuery-backend main does not depend on asap_executor yet. The backend's sample ingestion becomes a TimeSeries TableProvider.
Parent: #968 (PR 5 of 6; after #966). Related: #955 (this PR provides its lookback function and fixes its example 1), #951 (a shared scan reads the union scan_window returns).
Activity