Skip to content

Commit ad86098

Browse files
committed
apply low selectivity matchers lazily in ingester
Signed-off-by: yeya24 <[email protected]>
1 parent d0f3b96 commit ad86098

File tree

5 files changed

+401
-2
lines changed

5 files changed

+401
-2
lines changed

docs/configuration/config-file-reference.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3733,6 +3733,12 @@ instance_limits:
37333733
# CLI flag: -ingester.skip-metadata-limits
37343734
[skip_metadata_limits: <boolean> | default = true]
37353735
3736+
# Enable optimization of label matchers when query chunks. When enabled,
3737+
# matchers with low selectivity such as =~.+ are applied lazily during series
3738+
# scanning instead of being used for postings matching.
3739+
# CLI flag: -ingester.enable-matcher-optimization
3740+
[enable_matcher_optimization: <boolean> | default = false]
3741+
37363742
query_protection:
37373743
rejection:
37383744
threshold:

pkg/ingester/ingester.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ type Config struct {
162162
// If enabled, the metadata API returns all metadata regardless of the limits.
163163
SkipMetadataLimits bool `yaml:"skip_metadata_limits"`
164164

165+
// When enabled, matchers with low selectivity are applied lazily during series scanning
166+
// instead of being used for postings selection.
167+
EnableMatcherOptimization bool `yaml:"enable_matcher_optimization"`
168+
165169
QueryProtection configs.QueryProtection `yaml:"query_protection"`
166170
}
167171

@@ -185,6 +189,7 @@ func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
185189
f.BoolVar(&cfg.DisableChunkTrimming, "ingester.disable-chunk-trimming", false, "Disable trimming of matching series chunks based on query Start and End time. When disabled, the result may contain samples outside the queried time range but select performances may be improved. Note that certain query results might change by changing this option.")
186190
f.IntVar(&cfg.MatchersCacheMaxItems, "ingester.matchers-cache-max-items", 0, "Maximum number of entries in the regex matchers cache. 0 to disable.")
187191
f.BoolVar(&cfg.SkipMetadataLimits, "ingester.skip-metadata-limits", true, "If enabled, the metadata API returns all metadata regardless of the limits.")
192+
f.BoolVar(&cfg.EnableMatcherOptimization, "ingester.enable-matcher-optimization", false, "Enable optimization of label matchers when query chunks. When enabled, matchers with low selectivity such as =~.+ are applied lazily during series scanning instead of being used for postings matching.")
188193

189194
cfg.DefaultLimits.RegisterFlagsWithPrefix(f, "ingester.")
190195
cfg.QueryProtection.RegisterFlagsWithPrefix(f, "ingester.")
@@ -2295,6 +2300,10 @@ func (i *Ingester) queryStreamChunks(ctx context.Context, db *userTSDB, from, th
22952300
End: through,
22962301
DisableTrimming: i.cfg.DisableChunkTrimming,
22972302
}
2303+
var lazyMatchers []*labels.Matcher
2304+
if i.cfg.EnableMatcherOptimization {
2305+
matchers, lazyMatchers = optimizeMatchers(matchers)
2306+
}
22982307
// It's not required to return sorted series because series are sorted by the Cortex querier.
22992308
ss := q.Select(ctx, false, hints, matchers...)
23002309
c()
@@ -2308,14 +2317,19 @@ func (i *Ingester) queryStreamChunks(ctx context.Context, db *userTSDB, from, th
23082317
var it chunks.Iterator
23092318
for ss.Next() {
23102319
series := ss.At()
2320+
lbls := series.Labels()
2321+
2322+
if !labelsMatches(lbls, lazyMatchers) {
2323+
continue
2324+
}
23112325

2312-
if sm.IsSharded() && !sm.MatchesLabels(series.Labels()) {
2326+
if sm.IsSharded() && !sm.MatchesLabels(lbls) {
23132327
continue
23142328
}
23152329

23162330
// convert labels to LabelAdapter
23172331
ts := client.TimeSeriesChunk{
2318-
Labels: cortexpb.FromLabelsToLabelAdapters(series.Labels()),
2332+
Labels: cortexpb.FromLabelsToLabelAdapters(lbls),
23192333
}
23202334

23212335
it := series.Iterator(it)

pkg/ingester/matchers.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package ingester
2+
3+
import "slices"
4+
5+
import "github.com/prometheus/prometheus/model/labels"
6+
7+
// optimizeMatchers categorizes input matchers to matchers used in select and matchers applied lazily
8+
// when scanning series.
9+
func optimizeMatchers(matchers []*labels.Matcher) ([]*labels.Matcher, []*labels.Matcher) {
10+
// If there is only 1 matcher, use it for select.
11+
// If there is no matcher to optimize, also return early.
12+
if len(matchers) < 2 || !canOptimizeMatchers(matchers) {
13+
return matchers, nil
14+
}
15+
selectMatchers := make([]*labels.Matcher, 0, len(matchers))
16+
lazyMatchers := make([]*labels.Matcher, 0)
17+
for _, m := range matchers {
18+
// =~.* is a noop as it matches everything.
19+
if m.Type == labels.MatchRegexp && m.Value == ".*" {
20+
continue
21+
}
22+
if lazyMatcher(m) {
23+
lazyMatchers = append(lazyMatchers, m)
24+
continue
25+
}
26+
selectMatchers = append(selectMatchers, m)
27+
}
28+
29+
// We need at least 1 select matcher.
30+
if len(selectMatchers) == 0 {
31+
selectMatchers = lazyMatchers[:1]
32+
lazyMatchers = lazyMatchers[1:]
33+
}
34+
35+
return selectMatchers, lazyMatchers
36+
}
37+
38+
func canOptimizeMatchers(matchers []*labels.Matcher) bool {
39+
return slices.ContainsFunc(matchers, lazyMatcher)
40+
}
41+
42+
func labelsMatches(lbls labels.Labels, matchers []*labels.Matcher) bool {
43+
for _, m := range matchers {
44+
if !m.Matches(lbls.Get(m.Name)) {
45+
return false
46+
}
47+
}
48+
return true
49+
}
50+
51+
// lazyMatcher checks if the label matcher should be applied lazily when scanning series instead of fetching postings
52+
// for matcher. The matchers to apply lazily are matchers that are known to have low selectivity.
53+
func lazyMatcher(matcher *labels.Matcher) bool {
54+
if matcher.Value == ".+" && matcher.Type == labels.MatchRegexp {
55+
return true
56+
}
57+
if matcher.Value == "" && (matcher.Type == labels.MatchNotEqual || matcher.Type == labels.MatchNotRegexp) {
58+
return true
59+
}
60+
return false
61+
}

0 commit comments

Comments
 (0)