Skip to content
Open
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
3797d8b
Add benchmarks
charleskorn Feb 16, 2026
cd89492
Add CLI flag and validation
charleskorn Feb 5, 2026
6f28edb
Rename variables
charleskorn Feb 5, 2026
5d2faeb
Add metrics skeleton
charleskorn Feb 5, 2026
e95d418
Pass through feature flag
charleskorn Feb 5, 2026
b823b4d
Clarify comment
charleskorn Feb 5, 2026
01168c4
Refactor `EquivalentToIgnoringHintsAndChildren` implementations
charleskorn Feb 5, 2026
472a60e
Initial version of `SelectorsAreDuplicateOrSubset` without support fo…
charleskorn Feb 5, 2026
ae3ad8b
Extend `SelectorsAreDuplicateOrSubset` to identify subset selectors
charleskorn Feb 5, 2026
e278e44
Introduce `DuplicateFilter` node
charleskorn Feb 5, 2026
e1f6c73
Add materializer
charleskorn Feb 5, 2026
7c0e174
Add test cases
charleskorn Feb 5, 2026
11ca771
Add expected metrics to new test cases
charleskorn Feb 5, 2026
0f92b7b
Ensure SSE is only used if enabled and supported
charleskorn Feb 5, 2026
c461e26
Update comment
charleskorn Feb 5, 2026
45af904
Introduce `sharedSelectorGroup` type
charleskorn Feb 5, 2026
da4470d
Inject filter node if needed
charleskorn Feb 5, 2026
0b95d7b
Remove duplicate test case, fix reference numbers
charleskorn Feb 6, 2026
f00ccba
Initial incomplete implementation
charleskorn Feb 6, 2026
0c3d39e
Refactor
charleskorn Feb 6, 2026
2718e63
Fix incorrect test cases
charleskorn Feb 6, 2026
d153c81
Handle case where broader selector is seen after narrower selector
charleskorn Feb 6, 2026
468bf7d
Add support for lifting filtering out of expression when safe to do so
charleskorn Feb 6, 2026
fece92d
Use `selector` interface to simplify logic in `accumulatePath`
charleskorn Feb 8, 2026
0cdc86e
Apply filtering later if possible for unary and aggregate operations
charleskorn Feb 9, 2026
b35c9e8
Add note about binary operations
charleskorn Feb 9, 2026
e90d40a
Add two more test cases for edge cases
charleskorn Feb 9, 2026
c18e591
Add further test cases to `TestOptimizationPass_HintsHandling`
charleskorn Feb 9, 2026
accd95b
Extend multi-aggregation node to include filters
charleskorn Feb 9, 2026
0d17e29
Consider subset selectors in multi-aggregation optimisation pass
charleskorn Feb 9, 2026
9dfd03f
Add correctness test cases
charleskorn Feb 9, 2026
9dd9105
Implement filtering in multi-aggregation
charleskorn Feb 9, 2026
1872d33
Adjust tests to not succeed if filtering is incorrect
charleskorn Feb 9, 2026
341e47e
Add test cases and implementation for instant vector operator for hap…
charleskorn Feb 10, 2026
09af695
Discard unnecessary buffered data in instant vector operator as soon …
charleskorn Feb 13, 2026
53e97bd
Pass consumer pointer rather than consumer index, extract method for …
charleskorn Feb 13, 2026
352170e
Initial (complex) implementation for range vector operations
charleskorn Feb 16, 2026
8477a3b
Don't bother trying to discard series that would not have been buffer…
charleskorn Feb 16, 2026
e06cfb0
Don't bother examining series beyond the lagging consumer if all open…
charleskorn Feb 16, 2026
25108e3
Refactor instant vector operator's `CloseConsumer` to match range vec…
charleskorn Feb 16, 2026
788eb90
Simplify range vector operator logic
charleskorn Feb 16, 2026
cf77c96
Remove unused method
charleskorn Feb 16, 2026
edb592b
Fix panic if one consumer starts reading data before other consumers …
charleskorn Feb 16, 2026
80b380e
Fix issue where unused `SeriesMetadata` slices aren't returned to the…
charleskorn Feb 16, 2026
10170ed
Fix linting issues
charleskorn Feb 16, 2026
a25485e
Fix Protobuf formatting
charleskorn Feb 16, 2026
259b1e5
Fix error swallowing in applyDeduplication error handling
cursoragent Feb 17, 2026
4ccc0af
Fix broken test
charleskorn Feb 16, 2026
7498766
Add changelog entry
charleskorn Feb 17, 2026
115ba24
Address PR feedback: correctly handle filters on `__name__` for aggre…
charleskorn Feb 17, 2026
205242f
Merge branch 'main' into charleskorn/subset-selector-elimination
charleskorn Feb 17, 2026
594db7b
Turn off flag by default
charleskorn Feb 17, 2026
f5245c8
Merge branch 'main' into charleskorn/subset-selector-elimination
charleskorn Feb 19, 2026
a977eeb
Address PR feedback: add comments
charleskorn Feb 19, 2026
2182639
Address PR feedback: rename unused parameter
charleskorn Feb 19, 2026
e870a17
Address PR feedback: use `add` method
charleskorn Feb 19, 2026
4c81b1a
Use matchers generated at runtime if we can
charleskorn Feb 19, 2026
536be67
Don't load all series if only one consumer with filters present
charleskorn Feb 19, 2026
0e0524d
Address PR feedback: add comment
charleskorn Feb 20, 2026
80395ad
Merge branch 'main' into charleskorn/subset-selector-elimination
charleskorn Feb 20, 2026
42743dc
Address PR feedback: simplify `applyFiltering`
charleskorn Feb 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
* [ENHANCEMENT] Ingester: Add experimental file based Kafka consumer group offset tracking via flag `-ingest-storage.kafka.consumer-group-offset-commit-file-enforced`. #14110
* [ENHANCEMENT] Store-gateway: Add "OOO" column to the tenant blocks page to indicate whether each block was created from out-of-order samples. #14283
* [ENHANCEMENT] Ingester: Optimize ingestion from Kafka in clusters with mixed size tenants. #13924 #13961 #14302
* [ENHANCEMENT] MQE: Add experimental support for eliminating selectors that are a subset of another selector. Enable with `-querier.mimir-query-engine.enable-subset-selector-elimination=true`. #14377
* [BUGFIX] API: Fixed web UI links not respecting `-server.path-prefix` configuration. #14090
* [BUGFIX] Distributor: Fix issue where distributors didn't send custom values of native histograms. #13849
* [BUGFIX] Compactor: Fix potential concurrent map writes. #13053
Expand Down
11 changes: 11 additions & 0 deletions cmd/mimir/config-descriptor.json
Original file line number Diff line number Diff line change
Expand Up @@ -2537,6 +2537,17 @@
"fieldType": "boolean",
"fieldCategory": "experimental"
},
{
"kind": "field",
"name": "enable_subset_selector_elimination",
"required": false,
"desc": "Enable subset selector elimination when evaluating queries.",
"fieldValue": null,
"fieldDefaultValue": true,
"fieldFlag": "querier.mimir-query-engine.enable-subset-selector-elimination",
"fieldType": "boolean",
"fieldCategory": "experimental"
},
{
"kind": "field",
"name": "enable_narrow_binary_selectors",
Expand Down
2 changes: 2 additions & 0 deletions cmd/mimir/help-all.txt.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2179,6 +2179,8 @@ Usage of ./cmd/mimir/mimir:
[experimental] Enable pruning query expressions that are toggled off with constants. (default true)
-querier.mimir-query-engine.enable-reduce-matchers
[experimental] Enable eliminating duplicate or redundant matchers that are part of selector expressions. (default true)
-querier.mimir-query-engine.enable-subset-selector-elimination
[experimental] Enable subset selector elimination when evaluating queries. (default true)
-querier.minimize-ingester-requests
If true, when querying ingesters, only the minimum required ingesters required to reach quorum will be queried initially, with other ingesters queried only if needed due to failures from the initial set of ingesters. Enabling this option reduces resource consumption for the happy path at the cost of increased latency for the unhappy path. (default true)
-querier.minimize-ingester-requests-hedging-delay duration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1921,6 +1921,10 @@ mimir_query_engine:
# CLI flag: -querier.mimir-query-engine.enable-common-subexpression-elimination
[enable_common_subexpression_elimination: <boolean> | default = true]

# (experimental) Enable subset selector elimination when evaluating queries.
# CLI flag: -querier.mimir-query-engine.enable-subset-selector-elimination
[enable_subset_selector_elimination: <boolean> | default = true]

# (experimental) Enable generating selectors for one side of a binary
# expression based on results from the other side.
# CLI flag: -querier.mimir-query-engine.enable-narrow-binary-selectors
Expand Down
1 change: 1 addition & 0 deletions operations/mimir/mimir-flags-defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@
"querier.lookback-delta": 300000000000,
"querier.mimir-query-engine.enable-prune-toggles": true,
"querier.mimir-query-engine.enable-common-subexpression-elimination": true,
"querier.mimir-query-engine.enable-subset-selector-elimination": true,
"querier.mimir-query-engine.enable-eliminate-deduplicate-and-merge": true,
"querier.mimir-query-engine.enable-reduce-matchers": true,
"querier.mimir-query-engine.enable-multi-aggregation": true,
Expand Down
12 changes: 6 additions & 6 deletions pkg/querier/dispatcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,12 +565,12 @@ func TestDispatcher_HandleProtobuf(t *testing.T) {
types.NewRangeQueryTimeRange(startT, startT.Add(20*time.Second), 10*time.Second),
enableDelayedNameRemoval,
2,
[]string{"BinaryExpression: LHS + RHS", `LHS: VectorSelector: {__name__="my_three_item_series", idx=~"(0|1|2)"}`},
[]string{"BinaryExpression: LHS + RHS", `RHS: VectorSelector: {__name__="my_three_item_series"}`}, // Note that the wildcard selector has been removed by the "reduce matchers" pass.
[]string{"BinaryExpression: LHS + RHS", `LHS: DuplicateFilter: {idx=~"(0|1|2)"}`},
[]string{"BinaryExpression: LHS + RHS", `RHS: Duplicate`}, // Note that the wildcard selector has been removed by the "reduce matchers" pass.
),
expectedResponseMessages: []*frontendv2pb.QueryResultStreamRequest{
newSeriesMetadataMessage(
0,
2,
querierpb.SeriesMetadata{Labels: mimirpb.FromLabelsToLabelAdapters(labels.FromStrings(model.MetricNameLabel, "my_three_item_series", "idx", "0"))},
querierpb.SeriesMetadata{Labels: mimirpb.FromLabelsToLabelAdapters(labels.FromStrings(model.MetricNameLabel, "my_three_item_series", "idx", "1"))},
querierpb.SeriesMetadata{Labels: mimirpb.FromLabelsToLabelAdapters(labels.FromStrings(model.MetricNameLabel, "my_three_item_series", "idx", "2"))},
Expand All @@ -582,7 +582,7 @@ func TestDispatcher_HandleProtobuf(t *testing.T) {
querierpb.SeriesMetadata{Labels: mimirpb.FromLabelsToLabelAdapters(labels.FromStrings(model.MetricNameLabel, "my_three_item_series", "idx", "2"))},
),
newInstantVectorSeriesDataMessage(
0,
2,
querierpb.InstantVectorSeriesData{
Floats: []mimirpb.Sample{
{TimestampMs: 0, Value: 3},
Expand Down Expand Up @@ -616,7 +616,7 @@ func TestDispatcher_HandleProtobuf(t *testing.T) {
},
),
newInstantVectorSeriesDataMessage(
0,
2,
querierpb.InstantVectorSeriesData{
Floats: []mimirpb.Sample{
{TimestampMs: 0, Value: 5},
Expand All @@ -636,7 +636,7 @@ func TestDispatcher_HandleProtobuf(t *testing.T) {
},
),
newEvaluationCompletedMessage(stats.Stats{
SamplesProcessed: 18,
SamplesProcessed: 9,
QueueTime: 3 * time.Second,
WallTime: expectedQueryWallTime,
FetchedSeriesCount: 123,
Expand Down
38 changes: 31 additions & 7 deletions pkg/streamingpromql/benchmarks/benchmarks.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ import (
var MetricSizes = []int{1, 100, 2000}

type BenchCase struct {
Expr string
Steps int
InstantQueryOnly bool
Expr string
Steps int
InstantQueryOnly bool
IgnoreAnnotationDifferences bool
}

func (c BenchCase) Name() string {
Expand Down Expand Up @@ -418,6 +419,29 @@ func TestCases(metricSizes []int) []BenchCase {
{
Expr: "sum(sum_over_time(a_X[1d])) / sum(count_over_time(a_X[1d]))",
},
// Subset selector elimination cases
{
Expr: `a_X{l=~"[13579].*"} / a_X`,
},
{
Expr: `rate(a_X{l=~"[13579].*"}[1m]) / rate(a_X[1m])`,
IgnoreAnnotationDifferences: true, // The a_1 metric has no 'l' label, so this query selects no data, short-circuits and doesn't generate "metric might not be a counter" annotations.
},
{
Expr: `sum(a_X{l=~"[13579].*"}) / sum(a_X)`,
},
{
Expr: `sum(rate(a_X{l=~"[13579].*"}[1m])) / sum(rate(a_X[1m]))`,
IgnoreAnnotationDifferences: true, // The a_1 metric has no 'l' label, so this query selects no data, short-circuits and doesn't generate "metric might not be a counter" annotations.
},
{
Expr: `sum(a_X{l=~"[13].*"}) / sum(a_X)`,
},
{
Expr: `sum(rate(a_X{l=~"[13].*"}[1m])) / sum(rate(a_X[1m]))`,
IgnoreAnnotationDifferences: true, // The a_1 metric has no 'l' label, so this query selects no data, short-circuits and doesn't generate "metric might not be a counter" annotations.
},
// info()
{
Expr: `info(info_sparse_100, {__name__="target_info_X"})`,
},
Expand All @@ -439,7 +463,7 @@ func TestCases(metricSizes []int) []BenchCase {
tmp = append(tmp, c)
} else {
for _, count := range metricSizes {
tmp = append(tmp, BenchCase{Expr: strings.ReplaceAll(c.Expr, "X", strconv.Itoa(count)), Steps: c.Steps, InstantQueryOnly: c.InstantQueryOnly})
tmp = append(tmp, BenchCase{Expr: strings.ReplaceAll(c.Expr, "X", strconv.Itoa(count)), Steps: c.Steps, InstantQueryOnly: c.InstantQueryOnly, IgnoreAnnotationDifferences: c.IgnoreAnnotationDifferences})
}
}
}
Expand All @@ -460,9 +484,9 @@ func TestCases(metricSizes []int) []BenchCase {

tmp = append(tmp, c)
} else {
tmp = append(tmp, BenchCase{Expr: c.Expr, Steps: 0})
tmp = append(tmp, BenchCase{Expr: c.Expr, Steps: 100})
tmp = append(tmp, BenchCase{Expr: c.Expr, Steps: 1000})
tmp = append(tmp, BenchCase{Expr: c.Expr, Steps: 0, IgnoreAnnotationDifferences: c.IgnoreAnnotationDifferences})
tmp = append(tmp, BenchCase{Expr: c.Expr, Steps: 100, IgnoreAnnotationDifferences: c.IgnoreAnnotationDifferences})
tmp = append(tmp, BenchCase{Expr: c.Expr, Steps: 1000, IgnoreAnnotationDifferences: c.IgnoreAnnotationDifferences})
// Important: if adding test cases with larger numbers of steps, make sure to adjust NumIntervals as well.
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/streamingpromql/benchmarks/comparison_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func TestBothEnginesReturnSameResultsForBenchmarkQueries(t *testing.T) {
prometheusResult, prometheusClose := c.Run(ctx, t, start, end, interval, prometheusEngine, q)
mimirResult, mimirClose := c.Run(ctx, t, start, end, interval, mimirEngine, q)

testutils.RequireEqualResults(t, c.Expr, prometheusResult, mimirResult, false)
testutils.RequireEqualResults(t, c.Expr, prometheusResult, mimirResult, c.IgnoreAnnotationDifferences)

prometheusClose()
mimirClose()
Expand Down
3 changes: 3 additions & 0 deletions pkg/streamingpromql/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type EngineOpts struct {

EnablePruneToggles bool `yaml:"enable_prune_toggles" category:"experimental"`
EnableCommonSubexpressionElimination bool `yaml:"enable_common_subexpression_elimination" category:"experimental"`
EnableSubsetSelectorElimination bool `yaml:"enable_subset_selector_elimination" category:"experimental"`
EnableNarrowBinarySelectors bool `yaml:"enable_narrow_binary_selectors" category:"experimental"`
EnableEliminateDeduplicateAndMerge bool `yaml:"enable_eliminate_deduplicate_and_merge" category:"experimental"`
EnableReduceMatchers bool `yaml:"enable_reduce_matchers" category:"experimental"`
Expand All @@ -41,6 +42,7 @@ type EngineOpts struct {
func (o *EngineOpts) RegisterFlags(f *flag.FlagSet) {
f.BoolVar(&o.EnablePruneToggles, "querier.mimir-query-engine.enable-prune-toggles", true, "Enable pruning query expressions that are toggled off with constants.")
f.BoolVar(&o.EnableCommonSubexpressionElimination, "querier.mimir-query-engine.enable-common-subexpression-elimination", true, "Enable common subexpression elimination when evaluating queries.")
f.BoolVar(&o.EnableSubsetSelectorElimination, "querier.mimir-query-engine.enable-subset-selector-elimination", true, "Enable subset selector elimination when evaluating queries.")
f.BoolVar(&o.EnableNarrowBinarySelectors, "querier.mimir-query-engine.enable-narrow-binary-selectors", false, "Enable generating selectors for one side of a binary expression based on results from the other side.")
f.BoolVar(&o.EnableEliminateDeduplicateAndMerge, "querier.mimir-query-engine.enable-eliminate-deduplicate-and-merge", true, "Enable eliminating redundant DeduplicateAndMerge nodes from the query plan when it can be proven that each input series produces a unique output series.")
f.BoolVar(&o.EnableReduceMatchers, "querier.mimir-query-engine.enable-reduce-matchers", true, "Enable eliminating duplicate or redundant matchers that are part of selector expressions.")
Expand All @@ -65,6 +67,7 @@ func NewTestEngineOpts() EngineOpts {

EnablePruneToggles: true,
EnableCommonSubexpressionElimination: true,
EnableSubsetSelectorElimination: true,
EnableNarrowBinarySelectors: true,
EnableEliminateDeduplicateAndMerge: true,
EnableReduceMatchers: true,
Expand Down
1 change: 1 addition & 0 deletions pkg/streamingpromql/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func NewEngine(opts EngineOpts, limitsProvider QueryLimitsProvider, metrics *sta
planning.NODE_TYPE_DROP_NAME: planning.NodeMaterializerFunc[*core.DropName](core.MaterializeDropName),

planning.NODE_TYPE_DUPLICATE: planning.NodeMaterializerFunc[*commonsubexpressionelimination.Duplicate](commonsubexpressionelimination.MaterializeDuplicate),
planning.NODE_TYPE_DUPLICATE_FILTER: planning.NodeMaterializerFunc[*commonsubexpressionelimination.DuplicateFilter](commonsubexpressionelimination.MaterializeDuplicateFilter),
planning.NODE_TYPE_STEP_INVARIANT_EXPRESSION: planning.NodeMaterializerFunc[*core.StepInvariantExpression](core.MaterializeStepInvariantExpression),
planning.NODE_TYPE_MULTI_AGGREGATION_GROUP: planning.NodeMaterializerFunc[*multiaggregation.MultiAggregationGroup](multiaggregation.MaterializeMultiAggregationGroup),
planning.NODE_TYPE_MULTI_AGGREGATION_INSTANCE: planning.NodeMaterializerFunc[*multiaggregation.MultiAggregationInstance](multiaggregation.MaterializeMultiAggregationInstance),
Expand Down
13 changes: 3 additions & 10 deletions pkg/streamingpromql/optimize/ast/sort_labels_and_matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import (
"context"
"fmt"
"slices"
"strings"

"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql/parser"

"github.com/grafana/mimir/pkg/streamingpromql/planning/core"
)

// SortLabelsAndMatchers is an optimization pass that ensures that all label names and matchers are sorted.
Expand Down Expand Up @@ -54,13 +55,5 @@ func (s *SortLabelsAndMatchers) Apply(_ context.Context, expr parser.Expr) (pars
}

func compareMatchers(a, b *labels.Matcher) int {
if a.Name != b.Name {
return strings.Compare(a.Name, b.Name)
}

if a.Type != b.Type {
return int(a.Type - b.Type)
}

return strings.Compare(a.Value, b.Value)
return core.CompareMatchers(a.Name, b.Name, a.Type, b.Type, a.Value, b.Value)
}
Loading