Group the PromQL per-series aggregation by the raw id column - #113644
Group the PromQL per-series aggregation by the raw id column#113644nikitamikhaylov wants to merge 1 commit into
Conversation
The SQL generated for a PromQL selector converted the series id to a tag group once per sample: the selector emitted timeSeriesIdToGroup(id) AS group and the per-series (grid) aggregation grouped by the converted value. Now StoreMethod::RAW_DATA pieces carry the raw id column, its consumers run GROUP BY id and project timeSeriesIdToGroup(id) AS group right after the aggregation, so the lookup runs once per series instead of once per sample and the rest of the generated plan is unchanged. As a side benefit the GROUP BY key of the heaviest aggregation is now the leading sorting-key column of the samples table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Workflow [PR], commit [9531ef3] Summary: ✅
AI ReviewSummaryThis PR speeds up PromQL over Findings❌ Blockers
Final Verdict❌ Changes requested: the selector paths need to keep the LLVM Coverage Report
Changed lines: Changed C/C++ lines covered: 43/45 (95.56%) · Uncovered code |
|
|
||
| if (has_group) | ||
| builder.group_by.push_back(make_intrusive<ASTIdentifier>(ColumnNames::Group)); | ||
| builder.group_by.push_back(make_intrusive<ASTIdentifier>(group_by_raw_id ? ColumnNames::ID : ColumnNames::Group)); |
There was a problem hiding this comment.
StoreMethod::VECTOR_GRID still promises one row per group, but GROUP BY id no longer guarantees that. The new duplicate-id test demonstrates the bad case: last_over_time(...) and plain selectors can now produce two rows with the same group whenever two inner-table ids carry the same tag set.
That is more than an edge-case presentation change. Unchanged vector operators still rely on group uniqueness: applySimpleBinaryOperator.cpp:118-152 skips timeSeriesThrowDuplicateSeriesIf when join_group is just group and then uses ANY joins. A query like http_requests_total{ns="a",pod="p1"} == http_requests_total{ns="a",pod="p1"} can therefore drop one duplicate silently instead of raising the existing Multiple series have the same tags exception.
We need to re-establish uniqueness before returning VECTOR_GRID here, or explicitly throw on duplicate timeSeriesIdToGroup(id) values.
| time_series->setAlias(ColumnNames::TimeSeries); | ||
|
|
||
| group_by.push_back(make_intrusive<ASTIdentifier>(ColumnNames::Group)); | ||
| group_by.push_back(make_intrusive<ASTIdentifier>(ColumnNames::ID)); |
There was a problem hiding this comment.
This RAW_DATA finalizer has the same duplicate-labelset problem on the top-level range-selector path. GROUP BY id means prometheusQuery('ts', 'http_requests_total{...}[90s]', ...) now returns one row per raw id, not one row per label set, so two ids with identical tags surface as duplicate tags rows; the new stateless test pins exactly that behavior.
For PromQL results we normally reject duplicate series instead of returning an invalid vector, and the rest of this subsystem already uses timeSeriesThrowDuplicateSeriesIf to enforce that contract. Please keep the final range-vector output unique by timeSeriesIdToGroup(id) or throw when several ids map to the same group.
|
📊 Cloud Performance Report ✅ AI verdict: This change is confined to PromQL-to-SQL generation for the experimental TimeSeries engine (grouping raw samples by id instead of precomputed group), plus a new perf test and stateless test. The only flagged query, ClickBench Q18, does not touch any of that code path, so its apparent 9.5% improvement is run-to-run variance and has been downgraded to not_sure. No genuine PR-attributable regressions or improvements were found in the flagged set. clickbenchFlagged queries (1 of 43)
Change = percent below ×2; the ratio of medians (×N faster/slower) beyond, where percent understates the scale. q-value = BH-FDR adjusted p; smaller is stronger evidence. MIRAI flags a query when q < fdr_q (default 0.10) — the value the verdict is based on. tpch_adapted_1_official🟢 No significant changes Debug info
|
Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):
Speeds up PromQL queries over
TimeSeriestables: the per-series aggregation now groups raw samples by the rawidcolumn and converts the id to a tag group withtimeSeriesIdToGrouponce per series after the aggregation, instead of once per sample before it.Description
A PromQL selector transpiled to
so the id→group lookup in the per-query tags collector (
ContextTimeSeriesTagsCollector::getGroupByID) ran once per sample — ~4.4 billion times for a 30-day range query over 25,600 series.StoreMethod::RAW_DATAnow carries the rawidcolumn; its consumers aggregate withGROUP BY idand projecttimeSeriesIdToGroup(id) AS groupright after:so the lookup runs once per series, and every later step of the generated statement is byte-identical (verified by diffing the full generated SQL).
Measured on a TimeSeries table with 62.5 billion samples (32 cores), cold, results fingerprint-identical:
sum by(ns)(rate(m[5m])), 30 d range, 25.6k seriesquantile(0.95, gauge), 30 d range, 25.6k seriesunless-join alert shapelabel_replacejoinComplementary to #113580 (memoized lookups): measured on top of it, this PR still gains an additional -12% to -39% on the same shapes, because it removes the per-sample conversion instead of accelerating it.
Semantics are preserved: the collector is populated by the tags subquery (
CreatingSets) before the samples scan, and grouping byidequals grouping bygroupbecause distinct ids cannot share a tag set (the id is a hash of(metric_name, tags)). The one deliberate edge-case change — two distinct ids carrying an identical tag set, constructible only by manually inserting inconsistent rows into the inner tags table — now yields one row per id from selectors, andrate()-style paths fail with the existing "Multiple series have the same tags" check instead of silently merging; the test pins all three behaviors. As a side benefit, the heaviestGROUP BYkey is now the leading sorting-key column of the samples table, which is groundwork for order-based aggregation later.Tests:
04812_prometheus_query_group_by_raw_id.sql(instant + range:rate,sum by,group_left/unlessjoins,@modifier, an error canary for a pre-existing unrelatedapplyOffsetscale bug, the duplicated-tag-set edge case) andtests/performance/promql_query.xml(5,000 series, 7.2M samples).🤖 Generated with Claude Code