Repository navigation
Query engine: merge sketches across subpopulations (roll-up to a coarser grouping) #839
Copy link
Copy link
Closed
Labels
Description
Goal
Let the query engine answer a query grouped by label set Q from a precomputed aggregation grouped by G ⊋ Q by merging the sketches of every subpopulation that shares the same Q-projection (a roll-up). Today the engine only merges across time.
Example: KLL deployed by (service, endpoint). Query quantile by (service) (0.5, ...). Answer it by merging, per output step, all (service, *) KLLs into one KLL per service, then querying the quantile. Mergeable summaries (KLL, DDSketch, HLL, CMS, Sum, MinMax, Increase) keep their error guarantee under this merge, which re-aggregating final scalars (quantile-of-quantiles) does not.
Current state
- Matcher:
labels_compatible(asap_types/src/capability_matching.rs:153) is exact-equality only (TODO in place, tracked in labels_compatible() is exact-match only, not superset matching #649). The testlabel_strict_superset_rejectedpins this behaviour. - Engine:
estimate_range_query(simple_engine/mod.rs:2690) merges buckets per store group key across time (window_merger.rs,merge_utils.rs). Different group keys are never merged together. - Output labels:
query_output_labels = requirements.grouping_labels. Store-key rows are zipped by position with those labels (utils/http.rs:128), which is only correct becauseG == Qtoday. - Planner:
milp_output.rs:96-110refuses roll-ups explicitly (RollupUnsupported, testrollup_is_refused), because the engine can't serve them. - Existing scalar regrouping (
engines/query_time_aggregation.rs,nested_aggregation_plan) regroups final f64 values. It is correct for sum-of-sums but wrong for quantiles, so it does not cover this case.
Scope sketch
- Matcher: accept
config.grouping_labels ⊇ req.grouping_labelswhen the value type is mergeable (labels_compatible() is exact-match only, not superset matching #649). Prefer an exact match over a superset match. - Engine: after the per-key time merge, project each store key onto
Q(index map fromGtoQ), bucket the keys by projection, and fold each bucket withmerge_with(or the batch merge inmerge_utils.rs). Then query the statistic and emit rows labelled withQ. - Planner: lift
RollupUnsupportedonce the engine supports roll-ups, so the MILP can share one deployment across AQEs (No cross-AQE sharing — greedy deploys one sketch per AQE even when they could share #650).
Open questions
- Keyed types (MultipleSum, CMS, CMS+heap, HydraKLL) put labels in
aggregated_labelswith an emptygrouping(Capability-matching fallback never matches keyed aggregations (grouping_labels = []) #779). Do roll-ups overaggregatedlabels belong in scope? For CMS, a subset query would need every matching sub-key enumerated from the DeltaSet key aggregator, then the point estimates summed, and the error grows with the fan-in. - Merge order: merge across subpops first then across time, or the other way round? The result is the same for commutative merges, but cost differs. DeltaSet is order-sensitive.
- Cost: the fan-in can be large (endpoint cardinality). Is a batch merge needed from day one?
- Instant vs range: both go through
estimate_range_query, so one change should cover both. - SQL: the same thing for
GROUP BY serviceover a(service, endpoint)config.
Reactions are currently unavailable
Activity
milindsrivastava1997 commentedon Oct 13, 2026
Resolved design (v1)
Terms.
Gis the value config'sgrouping_labels.Qis the query's output labels (query_output_labels).G ⊋ Q, andQueryConfigdoesn't change. Capability matching is not involved: it only runs when noquery_configmatches the query string.aggregated_labelsand no separate key aggregation, i.e. KLL, DDSketch, HLL, Sum and MinMax. Increase is excluded. Keyed configs (Multiple*, CMS, CMS+heap, HydraKLL, topk-by) keep today's path; their roll-ups are Query engine: roll-ups over keyed aggregations (aggregated_labels) #846.G-key inComposedRangeRead.groupsto itsQ-projection and append its buckets into the sameBucketMapslot (start_ts → Vec).estimate_range_querystays unchanged: one batchget_merged()per (Q-key, step) merges across time and subpopulations together,fallback_keybecomes the Q-key so output labels are right, and instant queries share the path. Rejected alternatives: inside the store (leaks query semantics into the store trait), a second merge after the per-key time merge (restructures the step loop and costs N+1 merges), andquery_time_aggregations(a quantile of quantiles is wrong).G-key) stays as is.rollup_plan(G, Q, type) -> Result<Option<Projection>, _>in the shared context path covers PromQL, SQL and Elastic, gated on single-subpopulation configs.G == QreturnsNone(today's path).G ⊋ Qwith a v1 type returns a projection.Q ⊄ Gand an unsupported type both returnErr(Native), not a fall-through to Prometheus, because both mean the planner or config is wrong.query_configpointing at theG-grouped aggregation. The engine infers the roll-up.planned_subquery(thebyclause rewritten toGon the AST) plusquery_time_aggregations = [{Sum|Min|Max, by Q}]. Count uses Sum. No engine change is needed.RollupUnsupported. sketch-bench'smergeable_across_groupsnever proposes them anyway.aggregation_configtakes its labels fromdeployment.grouping_labels, not the first served item.Tests
rollup_planand the re-key, covering label reordering,Q = ∅, same-projection appends at the samestart_ts, and the error cases.SimpleEngine(instant and range): compare a(service, endpoint)config against a(service)config fed the same samples. Sum and MinMax must be exactly equal. For KLL, DD and HLL, the result must equal a manualmerge_withof the stored sketches and be within the sketch's error bound of the truth.G == Qoutput identical to before.Suite.planner_output(fixed streaming and inference YAML, skipping the planner), plus arollup.yamlsuite compared against Prometheus. Its dataset includes an endpoint that appears mid-range.PR plan
rollup_plan, the re-key, and test layers 1–3.planner_outputoverride plus therollup.yamlsuite (layer 4). Depends on 2.rollup_is_refusedtest.planned_subqueryplusquery_time_aggregations.PRs 4 and 5 are independent of 1–3. Follow-ups: #649 (matcher superset) and #846 (keyed roll-ups).