Repository navigation
Conversation
…ompile once; per-sample panes from columns Re-pin ASAPPlanner to main 92bc1296: the executor runs every operator on Arrow batches, compiled plans and RawSampleSelector are Send + Sync, and StateColumnUpdater builds a state column sample by sample. - Compile once: the plan's ingestion side (Ingestion) and its query roots (QueryEngine) are compiled when the plan is installed and shared by every ingestion worker and query thread through Arc. - Raw samples to columns: a raw-sample window's input batch is built from the samples' times and values and each series' cached cells (series identity and label columns, dictionary-encoded), with the Scan's matchers applied once per series through RawSampleSelector. Query-time raw windows, kept panes and the batch-build fallback all use it. - Ingestion per sample, no rows: a definition whose group is a series runs StateColumnUpdater::update in place per sample; at pane close each worker seals its states and builds its part with Batch::try_from_columns. Workers own disjoint series, so a pane is the workers' batches, shared by Arc in the SDS. Another shape keeps the plan's build, now run once over every worker's samples of the pane. - Stored panes bind through Operator::source, which treats them as held outside the run. - Stored panes are kept one pane width longer than the plan's retention: an evaluation at T runs once samples just after T have arrived, so the newest stored pane can be past T. - Rows are read only to encode answers as Prometheus JSON.
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.
Stack 5/5. Base: #831.
Why
The data plane's cost was the executor's row engine (
Vec<Vec<Value>>batches, row clones, per-row grouping keys and accounting), not the summaries. ASAPPlanner main now runs every operator on Arrow batches, compiles plans that areSend + Sync, and offersStateColumnUpdaterfor per-sample state columns. This PR moves the backend onto that: compile once, build panes per sample from columns, feed raw windows as columns, and read rows only to write the answer.What
Re-pin to ASAPPlanner main
92bc1296.Compile once. The plan's ingestion side (
Ingestion) and its query roots (QueryEngine) are compiled when the plan is installed and shared by every ingestion worker and query thread throughArc. The per-thread compile is gone.Raw samples to columns (
series.rs). A raw-sample window's input batch is built from the samples' times and values and each series' cached cells: its series identity and label columns, dictionary-encoded.RawSampleSelectorapplies theScan's matchers once per series. Query-time raw windows from the local raw buffer or remote read, kept panes, and the batch-build fallback all use it. NoSampleand no row is made per sample.Ingestion per sample, no rows. For a definition whose group is a series (its group columns include the series identity):
StateColumnUpdater::updateruns in place per sample;Batch::try_from_columns, with the group texts, the pane's timestamp and the state column;Arc<Vec<Batch>>and shared by reference.Other shapes keep the plan's own build, now run once over every worker's samples of the pane, so a group spanning workers is never split. This replaces feat(precompute): update each open pane's summary state per sample #830's
create_planner_accumulatorpath.Stored panes bind through
Operator::source, which treats them as held outside the run, so their states are not charged to the query.Pane retention margin. Stored panes are kept one pane width longer than the plan's
retention_ms: an evaluation at T runs once samples just after T have arrived, so the newest stored pane can be past T.How
SeriesWindow(one per raw-sample window, shared): it caches each series' cells orNone(not selected) under a read-write lock, keyed by label set; workers and query threads look a series up once per run of its samples.batch(end, samples)builds the time, value and dictionary text columns and callsBatch::try_from_columns.PerSampleSpec(one per definition, shared) reads theSummaryAggpayload for the value, time and group columns, as the executor's lowering does. It maps each output text column to the group column of its name. It finds the output timestamp's offset from the pane end by running the plan's build once over one row.OpenPanes(per worker) numbers the worker's groups and holds oneStateColumnUpdaterper open pane; a pane's part is the touched groups, in group-key order as the batch build emits them.Arc<Ingestion>andArc<QueryEngine>once inEngineHandle::spawn.Deviation from the requested design. "Merge the shards' updaters" applies to groups that span workers. Every ingestion-time build the Planner selects for the examples is per series, so workers never share a group and there is nothing to merge. An empty KLL allocates its full item buffer, so aligning group numbers across workers would cost every worker an empty sketch per other worker's series per pane. Such a build keeps the plan's build over all workers' samples instead. The Planner placed no
by-grouped build at ingestion for the queries tried (sum by (job) (count_over_time(...)),sum by (job) (sum_over_time(...)),sum(rate(...)),max by (job) (max_over_time(...))): without raw data they were all rejected asRawDataNotRetained.Rows.
grep -rn "to_rows\|Batch::try_new\|Vec<Vec<Value>>" data_plane/srcfinds one use:query.rsresult_samples, which reads the root's result rows to encode the Prometheus JSON answer. Tests also read rows to compare panes (tests/per_sample.rs).Transport, not rows. Raw samples still travel from the raw buffer and remote read to the query thread as
(Arc<labels>, time, value)triples, whichSeriesWindowencodes into columns per step.Before this PR
#831: row batches throughout; each thread compiled its own plan; per-sample ingestion went through
create_planner_accumulator, then rows; each worker's partial batch-build pane was concatenated (a group spanning workers would repeat). Panes were kept exactly the plan's retention, and the no-raw runs forwarded 160 of 481 evaluations of eachexample1query.After this PR
Arrow batches end to end, one compiled plan, per-sample panes built from columns, no forwards from retention. Unplanned queries are forwarded as before.
Verification
cargo test --workspacepasses, includinge2e_examples(10: Examples 1, 3a, 3b with and without raw data, 4a, 4b, B3 kept results, and range queries over raw windows, stored panes and kept panes),kept,range,answers,raw_buffer. Every answer equals the Planner executor's query-time reference.per_sample::per_sample_panes_equal_the_batch_build: for Example 1 (counter rate, sum, 10 s panes), Example 3b (KLL, 1 min) and Example 4b (KLL, 10 min), on 1 and 4 shards, every pane built per sample equals the plan's own build over the same samples. Exact states are compared by serialized bytes, KLL by quantiles at every percent.precompute::keeps_the_panes_of_an_evaluation_behind_the_newest_pane, failing without the margin: an evaluation at 10:00 still finds the 10-s pane it reads at offset 50 s after samples through 10:15.keeps_panes_for_the_plans_retention,range::every_step_reads_stored_panesandanswers::forwards_whole_range_queries_a_step_cannot_answermove their evicted pane one width back.Benefit harness (release, Prometheus 3.5.0, same session)
Same setup as #828: examples workload, 90 min of data, one sample per series every 15 s, AWS Fargate us-east-1 prices with measured operation costs. Prometheus is pinned to 4 cores and the data plane to another 4. "Prometheus alone" is the baseline run of the col experiment; "pr4" is #831's head, measured earlier. CPU is core-seconds over the whole run; latency p50 / p99 ms. Every evaluation in every col run meets its accuracy target. The 5 forwarded evaluations without raw data are Example 4b's first ones, before 60 minutes of panes exist (also forwarded before this PR).
s10
s10-noraw
s50
s50-noraw
Data plane + its Prometheus, against Prometheus alone (CPU core-seconds): s10 with raw 21.9 vs 19.7; s10 without 25.8 vs 19.3; s50 with raw 96.7 vs 88.8; s50 without 117.7 vs 85.7. The ASAP run's Prometheus still ingests every sample.
Where data-plane CPU goes now
perf record -F 499 -gon the data plane for a whole s50 run, with and without raw data (a separate run of each config, reusing its plan). Shares are of data-plane CPU.memcpy/memset/memcmpBTreeMapequality)What remains:
KLL::init_internal). The zeroing shows up asmemsetand kernel page faults, about a third of the CPU, in every pane and every merge. A KLL that allocated lazily, or reused buffers, would remove most of it (an asap_sketchlib change).BTreeMap<String, String>(SipHash) in the backend's per-series caches and the raw buffer: 4–6%. Interning label sets to series ids at Remote Write decode removes this.