Repository navigation
feat: range evaluations (step, span) in the workload; pane grid, per-step pricing and pane retention - #759
Merged
Conversation
A dashboard panel sends one query_range request per refresh, which
evaluates the query at every step over the last span. QueryWorkloadEntry
(and BatchEntry/RepeatingEntry, which normalize into it) now carry an
optional RangeEvaluation { step_ms, span_ms } next to the recurrence,
which still says how often a request runs. Validation rejects a zero
step and more steps per request than Prometheus allows (11,000). Omitted,
nothing changes and entries serialize as before.
Stage 1 splits windows into panes on every reader's evaluation grid, gcd(interval, step) with a range evaluation, and Stage 2 rejects a pane chain off a reader's step grid and keeps range-evaluated whole-window builds at query time. Stage 3 prices span/step + 1 evaluations per request, builds a chain kept across evaluations once per request, and retains kept and ingestion-time panes for the window plus the span; the selected plan reports that retention (Selection::pane_retention_ms). stage_pipeline plans a PlanningWorkload JSON file (--workload) and shows both. Without a range evaluation every price is unchanged.
…iced once Review fixes for range evaluations: - Prometheus places query_range steps forward from start, so a span that is not a whole number of steps puts the last step off the end's pane grid. Reject it (RangeEvaluationSpanNotMultipleOfStep) and drop evaluated_span_ms, which then always equals span_ms. - A one-step range (span 0) no longer narrows the evaluation grid or triggers the pane-on-step check. - evaluation_rate counts readers with one interval and step at their most steps, not the sum; a once-per-request reader adds nothing beside a range on its interval. - One gcd (asap_types::workload::greatest_common_divisor). - Pane retention is computed for the selected candidate only.
…e interval Readers on one interval end their requests at the same instant, so a node they share runs once per distinct instant end - k*step. Readers at different steps (60 s over 1 h and 30 s over 10 min) shared 11 instants that were counted twice (82 a minute instead of 71). evaluation_rate now keeps the longest span per step and, when steps differ, counts the union of their offsets; once per request is the interval with span 0.
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
A dashboard panel refreshes on a cadence and sends one
query_rangerequest per refresh, for example the last hour at 1-minute steps. Each request evaluates the query at 61 steps. A workload entry could only say how often a request runs (QueryRecurrence), so:What
QueryWorkloadEntry,RepeatingEntryandBatchEntrytake an optionalrange_evaluation: {"step_ms", "span_ms"}.recurrencestill says how often a request runs.start = end − span_msthroughend.step_ms: 0, aspan_msthat is not a multiple ofstep_ms(the last step would fall off the end's grid), and more than 11,000 steps per request (Prometheus' limit).span_ms: 0) is treated as one evaluation per request in every stage.gcd(window, interval, step)instead ofgcd(window, interval). Every step therefore ends on a pane boundary.S = span_ms / step_ms + 1evaluations, and sharing across steps and refreshes is credited. Formula below.Selection::pane_retention_msgives, for each pane chain the selected plan keeps beyond one evaluation, the window plus the readers' span.stage_pipelineshows it asstage3_plan_selection.pane_retention_ms. feat(planner): plan_installable, the one call a deployment runtime makes (#749) #755 (the installable plan) has not merged yet. When it does, it will carry this value in its stored outputs.stage_pipeline --workload <file>plans aPlanningWorkloadJSON file. The document's workload section showsrange_evaluation.How
Stage 3's formula, for a query that runs
Rrequests per second, each withS = span_ms / step_ms + 1steps:NotMaterialized,MatAtQuery(every query-time computation not kept)R · Sper consumerMatAtIngestionstateR · SN + span_ms/wcompleted panes, plus the one being builtMatAtQueryKeptAcrossEvaluationschainR: in the steady state each request builds only its newest pane (Stage 2 offers it only when requests repeat at the pane width), and every other pane its steps read was built earlierN − 1 + span_ms/wpanes between requests, all of them at the peakThe retention reported to the runtime is
N·w + span_ms: the window plus the span. For a chain read by several queries, the longest span counts.Within
evaluation_rate, queries on one interval are evaluated together, and every request ends at the same instant. A node they share is computed once per distinct instantend − k·step_ms(k·step_ms ≤ span_ms) of any of them:span_ms/step_ms + 1for the longest span. A 61-step and an 11-step reader at 1-min steps count 61.step = interval, span 0, i.e. only the request's end, which every range already evaluates at.Queries on different intervals add. Latency stays per evaluation, i.e. per step. With no range,
S = 1and the retention is the window, so every formula reduces to today's.Sharing across refreshes works through the pane grid. Kept panes are offered only when
w == intervalandw | step, so the earliest step of every request falls on the grid even when the interval does not divide the span.Key code interfaces added / modified
Workload:
crates/types/src/workload/mod.rsStage 1:
crates/logical-asap-optimization/src/workload_candidates.rs,asap_aware_cse.rsStage 2:
crates/physical-asap-optimization/src/lib.rs(The crate-private
IngestionPruneReasongainsRangeEvaluatedWholeWindow.)Stage 3:
crates/plan-selection/src/lib.rsDevtool:
crates/devtools/src/bin/stage_pipeline.rs--workload <file>(new): plans aPlanningWorkloadJSON file. It cannot be combined with--exampleor--promql/--sql.--tabledeclares the tables an SQL workload reads.stage3_plan_selection.pane_retention_ms(new,{node_id: ms}), andworkload.queries[i].requirements.range_evaluation(new, when declared).Usage (end to end, run at this head)
Inside the run:
evaluation_grid_ms= gcd(60 s, 60 s) = 60 s, so the 5-min window becomes five 1-min panes.NotMaterialized,MatAtIngestionand kept across evaluations.What comes back in
run.json:Without
range_evaluation, the same workload selectsP1(2.0833 cost/s), ahead ofP2-m2(4.5133) andP2-m1(6.5133).Before this PR
query_rangewas priced as one evaluation per refresh.After this PR
quantile_over_time(0.99, latency_ms[5m])every 1 min, 1M series, kept results allowed (cost/s, cost model 1){60000, 3600000}(61 steps)For Pattern B, the kept and ingestion-time panes are still not selected. Each pane holds a KLL for each of the 1M series, so the memory of 1 h of panes outweighs rebuilding at every step under the illustrative prices. This matches #754.
With per-series exact state (8 bytes a pane), declaring the range flips the choice, as in the Usage above:
max_over_time(m[5m])every 1 min, 1M seriesVerification
All tests below were written to fail first, the review fixes' tests included (span not a multiple of the step, one-step ranges, shared steps). With range handling disabled (one step per request, step ignored in the grid, Stage 2 check removed), the four new range tests and the two Stage 2 tests failed. They pass at this head.
cargo nextest run -p asap-integration-tests --test range_evaluation: 5 passed.stage3_plan_selection_prices_61_evaluations_per_dashboard_request: the Pattern B numbers in the table above, pinned.stage2_physical_asap_optimization_offers_only_step_aligned_pane_widths: 1-min refresh with 30 s steps gives only 30 s panes, and none is pruned. Without the range, the panes are 1 min.stage3_plan_selection_reports_the_retention_of_kept_panes: kept panes report 3,900,000 ms. B1 without a range reports 300,000 ms. A plan with no panes reports none.no_range_evaluation_leaves_every_price_unchanged: Pattern B's pinned 2.1, 2.18333, 8,884.53 and 106.6 GB are unchanged, andspan_ms: 0prices identically to no range.stage3_plan_selection_prefers_kept_panes_once_the_range_is_declared.asap-physical-asap-optimization: 16 passed, includinga_pane_chain_off_the_range_step_grid_is_rejected(a one-step range with a 30 s step accepts 1-min panes) anda_range_evaluated_whole_window_build_stays_at_query_time.asap-types:range_evaluation_is_optional_carried_and_validated(JSON, normalization, grid, validation bounds, a 90 s span at 60 s steps rejected, a one-step range with a 45 s step keeping the 60 s grid) and the doc tests.asap-plan-selection:readers_at_one_interval_and_step_share_their_steps(61-step, 11-step, no-range and one-step readers on one interval: 61 a minute; 1-min steps over 1 h with 30 s steps over 10 min: 71, the distinct instants).asap-devtools: thestage_pipelinetest binary, 26 passed (the whole crate, 40, at the first head), includingworkload_file_with_a_range_evaluation_is_planned. The committed Example 1 fixture changed only by the new empty"pane_retention_ms": {}; all its costs are identical.planner_layering_example3/example4/example1,tumbling_pane_merge,deployment_capabilities, plan-selection (all), logical-asap-optimization (all) and devtoolscost_model_flag…/deployment_capabilities_flag…(8,884.53).cargo fmt --all -- --checkpasses. Clippy (--all-targets --all-features --locked -D warnings) passes on types, logical, physical, plan-selection, devtools and integration-tests. The workspacecargo check --all-targetspasses after rebasing onto feat(plan-selection): AWS Fargate us-east-1 price preset for cost model 1 #756 and refactor!: name summary builds by shape; one RowWeight for keyed builds #758.Limitations and follow-ups
pane_retention_msin its installable plan's stored outputs.Closes #757