Skip to content

Commit c2c4827

Browse files
authored
Implement matchers cache on Ingester (#6477)
* Implement matcher cache Signed-off-by: alanprot <[email protected]> * changelog Signed-off-by: alanprot <[email protected]> * lint Signed-off-by: alanprot <[email protected]> * fix evict metric Signed-off-by: alanprot <[email protected]> --------- Signed-off-by: alanprot <[email protected]> Signed-off-by: Alan Protasio <[email protected]>
1 parent 12412e6 commit c2c4827

File tree

11 files changed

+199
-39
lines changed

11 files changed

+199
-39
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* [FEATURE] Distributor: Accept multiple HA Tracker pairs in the same request. #6256
2121
* [FEATURE] Ruler: Add support for per-user external labels #6340
2222
* [FEATURE] Query Frontend: Support an exemplar federated query when `-tenant-federation.enabled=true`. #6455
23+
* [FEATURE] Ingester: Add support for cache query matchers via `-ingester.matchers-cache-max-items. #6477
2324
* [ENHANCEMENT] Querier: Add a `-tenant-federation.max-concurrent` flags to configure the number of worker processing federated query and add a `cortex_querier_federated_tenants_per_query` histogram to track the number of tenants per query. #6449
2425
* [ENHANCEMENT] Query Frontend: Add a number of series in the query response to the query stat log. #6423
2526
* [ENHANCEMENT] Store Gateway: Add a hedged request to reduce the tail latency. #6388

docs/configuration/config-file-reference.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3181,6 +3181,10 @@ instance_limits:
31813181
# change by changing this option.
31823182
# CLI flag: -ingester.disable-chunk-trimming
31833183
[disable_chunk_trimming: <boolean> | default = false]
3184+
3185+
# Maximum number of entries in the matchers cache. 0 to disable.
3186+
# CLI flag: -ingester.matchers-cache-max-items
3187+
[matchers_cache_max_items: <int> | default = 0]
31843188
```
31853189

31863190
### `ingester_client_config`

integration/query_fuzz_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,8 +378,9 @@ func TestExpandedPostingsCacheFuzz(t *testing.T) {
378378
"-blocks-storage.expanded_postings_cache.head.enabled": "true",
379379
"-blocks-storage.expanded_postings_cache.block.enabled": "true",
380380
// Ingester.
381-
"-ring.store": "consul",
382-
"-consul.hostname": consul2.NetworkHTTPEndpoint(),
381+
"-ring.store": "consul",
382+
"-consul.hostname": consul2.NetworkHTTPEndpoint(),
383+
"-ingester.matchers-cache-max-items": "10000",
383384
// Distributor.
384385
"-distributor.replication-factor": "1",
385386
// Store-gateway.

pkg/distributor/distributor_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/prometheus/prometheus/tsdb/tsdbutil"
2727
"github.com/stretchr/testify/assert"
2828
"github.com/stretchr/testify/require"
29+
storecache "github.com/thanos-io/thanos/pkg/store/cache"
2930
"github.com/weaveworks/common/httpgrpc"
3031
"github.com/weaveworks/common/user"
3132
"go.uber.org/atomic"
@@ -3374,7 +3375,7 @@ func (i *mockIngester) Query(ctx context.Context, req *client.QueryRequest, opts
33743375
return nil, errFail
33753376
}
33763377

3377-
_, _, matchers, err := client.FromQueryRequest(req)
3378+
_, _, matchers, err := client.FromQueryRequest(storecache.NewNoopMatcherCache(), req)
33783379
if err != nil {
33793380
return nil, err
33803381
}
@@ -3400,7 +3401,7 @@ func (i *mockIngester) QueryStream(ctx context.Context, req *client.QueryRequest
34003401
return nil, errFail
34013402
}
34023403

3403-
_, _, matchers, err := client.FromQueryRequest(req)
3404+
_, _, matchers, err := client.FromQueryRequest(storecache.NewNoopMatcherCache(), req)
34043405
if err != nil {
34053406
return nil, err
34063407
}
@@ -3459,7 +3460,7 @@ func (i *mockIngester) MetricsForLabelMatchersStream(ctx context.Context, req *c
34593460
return nil, errFail
34603461
}
34613462

3462-
_, _, _, multiMatchers, err := client.FromMetricsForLabelMatchersRequest(req)
3463+
_, _, _, multiMatchers, err := client.FromMetricsForLabelMatchersRequest(storecache.NewNoopMatcherCache(), req)
34633464
if err != nil {
34643465
return nil, err
34653466
}
@@ -3491,7 +3492,7 @@ func (i *mockIngester) MetricsForLabelMatchers(ctx context.Context, req *client.
34913492
return nil, errFail
34923493
}
34933494

3494-
_, _, _, multiMatchers, err := client.FromMetricsForLabelMatchersRequest(req)
3495+
_, _, _, multiMatchers, err := client.FromMetricsForLabelMatchersRequest(storecache.NewNoopMatcherCache(), req)
34953496
if err != nil {
34963497
return nil, err
34973498
}

pkg/ingester/client/compat.go

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/prometheus/prometheus/model/labels"
88
"github.com/prometheus/prometheus/storage"
99
"github.com/prometheus/prometheus/tsdb/chunkenc"
10+
storecache "github.com/thanos-io/thanos/pkg/store/cache"
1011

1112
"github.com/cortexproject/cortex/pkg/cortexpb"
1213
)
@@ -26,8 +27,8 @@ func ToQueryRequest(from, to model.Time, matchers []*labels.Matcher) (*QueryRequ
2627
}
2728

2829
// FromQueryRequest unpacks a QueryRequest proto.
29-
func FromQueryRequest(req *QueryRequest) (model.Time, model.Time, []*labels.Matcher, error) {
30-
matchers, err := FromLabelMatchers(req.Matchers)
30+
func FromQueryRequest(cache storecache.MatchersCache, req *QueryRequest) (model.Time, model.Time, []*labels.Matcher, error) {
31+
matchers, err := FromLabelMatchers(cache, req.Matchers)
3132
if err != nil {
3233
return 0, 0, nil, err
3334
}
@@ -55,10 +56,10 @@ func ToExemplarQueryRequest(from, to model.Time, matchers ...[]*labels.Matcher)
5556
}
5657

5758
// FromExemplarQueryRequest unpacks a ExemplarQueryRequest proto.
58-
func FromExemplarQueryRequest(req *ExemplarQueryRequest) (int64, int64, [][]*labels.Matcher, error) {
59+
func FromExemplarQueryRequest(cache storecache.MatchersCache, req *ExemplarQueryRequest) (int64, int64, [][]*labels.Matcher, error) {
5960
var result [][]*labels.Matcher
6061
for _, m := range req.Matchers {
61-
matchers, err := FromLabelMatchers(m.Matchers)
62+
matchers, err := FromLabelMatchers(cache, m.Matchers)
6263
if err != nil {
6364
return 0, 0, nil, err
6465
}
@@ -175,10 +176,10 @@ func SeriesSetToQueryResponse(s storage.SeriesSet) (*QueryResponse, error) {
175176
}
176177

177178
// FromMetricsForLabelMatchersRequest unpacks a MetricsForLabelMatchersRequest proto
178-
func FromMetricsForLabelMatchersRequest(req *MetricsForLabelMatchersRequest) (model.Time, model.Time, int, [][]*labels.Matcher, error) {
179+
func FromMetricsForLabelMatchersRequest(cache storecache.MatchersCache, req *MetricsForLabelMatchersRequest) (model.Time, model.Time, int, [][]*labels.Matcher, error) {
179180
matchersSet := make([][]*labels.Matcher, 0, len(req.MatchersSet))
180181
for _, matchers := range req.MatchersSet {
181-
matchers, err := FromLabelMatchers(matchers.Matchers)
182+
matchers, err := FromLabelMatchers(cache, matchers.Matchers)
182183
if err != nil {
183184
return 0, 0, 0, nil, err
184185
}
@@ -206,12 +207,12 @@ func ToLabelValuesRequest(labelName model.LabelName, from, to model.Time, limit
206207
}
207208

208209
// FromLabelValuesRequest unpacks a LabelValuesRequest proto
209-
func FromLabelValuesRequest(req *LabelValuesRequest) (string, int64, int64, int, []*labels.Matcher, error) {
210+
func FromLabelValuesRequest(cache storecache.MatchersCache, req *LabelValuesRequest) (string, int64, int64, int, []*labels.Matcher, error) {
210211
var err error
211212
var matchers []*labels.Matcher
212213

213214
if req.Matchers != nil {
214-
matchers, err = FromLabelMatchers(req.Matchers.Matchers)
215+
matchers, err = FromLabelMatchers(cache, req.Matchers.Matchers)
215216
if err != nil {
216217
return "", 0, 0, 0, nil, err
217218
}
@@ -236,12 +237,12 @@ func ToLabelNamesRequest(from, to model.Time, limit int, matchers []*labels.Matc
236237
}
237238

238239
// FromLabelNamesRequest unpacks a LabelNamesRequest proto
239-
func FromLabelNamesRequest(req *LabelNamesRequest) (int64, int64, int, []*labels.Matcher, error) {
240+
func FromLabelNamesRequest(cache storecache.MatchersCache, req *LabelNamesRequest) (int64, int64, int, []*labels.Matcher, error) {
240241
var err error
241242
var matchers []*labels.Matcher
242243

243244
if req.Matchers != nil {
244-
matchers, err = FromLabelMatchers(req.Matchers.Matchers)
245+
matchers, err = FromLabelMatchers(cache, req.Matchers.Matchers)
245246
if err != nil {
246247
return 0, 0, 0, nil, err
247248
}
@@ -275,27 +276,31 @@ func toLabelMatchers(matchers []*labels.Matcher) ([]*LabelMatcher, error) {
275276
return result, nil
276277
}
277278

278-
func FromLabelMatchers(matchers []*LabelMatcher) ([]*labels.Matcher, error) {
279+
func FromLabelMatchers(cache storecache.MatchersCache, matchers []*LabelMatcher) ([]*labels.Matcher, error) {
279280
result := make([]*labels.Matcher, 0, len(matchers))
280281
for _, matcher := range matchers {
281-
var mtype labels.MatchType
282-
switch matcher.Type {
283-
case EQUAL:
284-
mtype = labels.MatchEqual
285-
case NOT_EQUAL:
286-
mtype = labels.MatchNotEqual
287-
case REGEX_MATCH:
288-
mtype = labels.MatchRegexp
289-
case REGEX_NO_MATCH:
290-
mtype = labels.MatchNotRegexp
291-
default:
292-
return nil, fmt.Errorf("invalid matcher type")
293-
}
294-
matcher, err := labels.NewMatcher(mtype, matcher.Name, matcher.Value)
282+
m, err := cache.GetOrSet(matcher.String(), func() (*labels.Matcher, error) {
283+
var mtype labels.MatchType
284+
switch matcher.Type {
285+
case EQUAL:
286+
mtype = labels.MatchEqual
287+
case NOT_EQUAL:
288+
mtype = labels.MatchNotEqual
289+
case REGEX_MATCH:
290+
mtype = labels.MatchRegexp
291+
case REGEX_NO_MATCH:
292+
mtype = labels.MatchNotRegexp
293+
default:
294+
return nil, fmt.Errorf("invalid matcher type")
295+
}
296+
return labels.NewMatcher(mtype, matcher.GetName(), matcher.GetValue())
297+
})
298+
295299
if err != nil {
296300
return nil, err
297301
}
298-
result = append(result, matcher)
302+
303+
result = append(result, m)
299304
}
300305
return result, nil
301306
}

pkg/ingester/client/compat_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/prometheus/common/model"
99
"github.com/prometheus/prometheus/model/labels"
10+
storecache "github.com/thanos-io/thanos/pkg/store/cache"
1011
)
1112

1213
func TestQueryRequest(t *testing.T) {
@@ -41,7 +42,7 @@ func TestQueryRequest(t *testing.T) {
4142
t.Fatal(err)
4243
}
4344

44-
haveFrom, haveTo, haveMatchers, err := FromQueryRequest(req)
45+
haveFrom, haveTo, haveMatchers, err := FromQueryRequest(storecache.NewNoopMatcherCache(), req)
4546
if err != nil {
4647
t.Fatal(err)
4748
}

pkg/ingester/ingester.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"github.com/thanos-io/objstore"
3838
"github.com/thanos-io/thanos/pkg/block/metadata"
3939
"github.com/thanos-io/thanos/pkg/shipper"
40+
storecache "github.com/thanos-io/thanos/pkg/store/cache"
4041
"github.com/thanos-io/thanos/pkg/store/storepb"
4142
"github.com/weaveworks/common/httpgrpc"
4243
"go.uber.org/atomic"
@@ -145,6 +146,9 @@ type Config struct {
145146
// When disabled, the result may contain samples outside the queried time range but Select() performances
146147
// may be improved.
147148
DisableChunkTrimming bool `yaml:"disable_chunk_trimming"`
149+
150+
// Maximum number of entries in the matchers cache. 0 to disable.
151+
MatchersCacheMaxItems int `yaml:"matchers_cache_max_items"`
148152
}
149153

150154
// RegisterFlags adds the flags required to config this to the given FlagSet
@@ -173,6 +177,7 @@ func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
173177
f.BoolVar(&cfg.LabelsStringInterningEnabled, "ingester.labels-string-interning-enabled", false, "Experimental: Enable string interning for metrics labels.")
174178

175179
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.")
180+
f.IntVar(&cfg.MatchersCacheMaxItems, "ingester.matchers-cache-max-items", 0, "Maximum number of entries in the matchers cache. 0 to disable.")
176181
}
177182

178183
func (cfg *Config) Validate() error {
@@ -243,6 +248,7 @@ type Ingester struct {
243248
inflightQueryRequests atomic.Int64
244249
maxInflightQueryRequests util_math.MaxTracker
245250

251+
matchersCache storecache.MatchersCache
246252
expandedPostingsCacheFactory *cortex_tsdb.ExpandedPostingsCacheFactory
247253
}
248254

@@ -708,7 +714,18 @@ func New(cfg Config, limits *validation.Overrides, registerer prometheus.Registe
708714
logger: logger,
709715
ingestionRate: util_math.NewEWMARate(0.2, instanceIngestionRateTickInterval),
710716
expandedPostingsCacheFactory: cortex_tsdb.NewExpandedPostingsCacheFactory(cfg.BlocksStorageConfig.TSDB.PostingsCache),
717+
matchersCache: storecache.NewNoopMatcherCache(),
718+
}
719+
720+
if cfg.MatchersCacheMaxItems > 0 {
721+
r := prometheus.NewRegistry()
722+
registerer.MustRegister(newMatchCacheMetrics(r, logger))
723+
i.matchersCache, err = storecache.NewMatchersCache(storecache.WithSize(cfg.MatchersCacheMaxItems), storecache.WithPromRegistry(r))
724+
if err != nil {
725+
return nil, err
726+
}
711727
}
728+
712729
i.metrics = newIngesterMetrics(registerer,
713730
false,
714731
cfg.ActiveSeriesMetricsEnabled,
@@ -1474,7 +1491,7 @@ func (i *Ingester) QueryExemplars(ctx context.Context, req *client.ExemplarQuery
14741491
return nil, err
14751492
}
14761493

1477-
from, through, matchers, err := client.FromExemplarQueryRequest(req)
1494+
from, through, matchers, err := client.FromExemplarQueryRequest(i.matchersCache, req)
14781495
if err != nil {
14791496
return nil, err
14801497
}
@@ -1564,7 +1581,7 @@ func (i *Ingester) labelsValuesCommon(ctx context.Context, req *client.LabelValu
15641581
return nil, cleanup, err
15651582
}
15661583

1567-
labelName, startTimestampMs, endTimestampMs, limit, matchers, err := client.FromLabelValuesRequest(req)
1584+
labelName, startTimestampMs, endTimestampMs, limit, matchers, err := client.FromLabelValuesRequest(i.matchersCache, req)
15681585
if err != nil {
15691586
return nil, cleanup, err
15701587
}
@@ -1654,7 +1671,7 @@ func (i *Ingester) labelNamesCommon(ctx context.Context, req *client.LabelNamesR
16541671
return nil, cleanup, err
16551672
}
16561673

1657-
startTimestampMs, endTimestampMs, limit, matchers, err := client.FromLabelNamesRequest(req)
1674+
startTimestampMs, endTimestampMs, limit, matchers, err := client.FromLabelNamesRequest(i.matchersCache, req)
16581675
if err != nil {
16591676
return nil, cleanup, err
16601677
}
@@ -1768,7 +1785,7 @@ func (i *Ingester) metricsForLabelMatchersCommon(ctx context.Context, req *clien
17681785
}
17691786

17701787
// Parse the request
1771-
_, _, limit, matchersSet, err := client.FromMetricsForLabelMatchersRequest(req)
1788+
_, _, limit, matchersSet, err := client.FromMetricsForLabelMatchersRequest(i.matchersCache, req)
17721789
if err != nil {
17731790
return cleanup, err
17741791
}
@@ -1982,7 +1999,7 @@ func (i *Ingester) QueryStream(req *client.QueryRequest, stream client.Ingester_
19821999
return err
19832000
}
19842001

1985-
from, through, matchers, err := client.FromQueryRequest(req)
2002+
from, through, matchers, err := client.FromQueryRequest(i.matchersCache, req)
19862003
if err != nil {
19872004
return err
19882005
}

pkg/ingester/ingester_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,64 @@ func seriesSetFromResponseStream(s *mockQueryStreamServer) (storage.SeriesSet, e
119119
return set, nil
120120
}
121121

122+
func TestMatcherCache(t *testing.T) {
123+
limits := defaultLimitsTestConfig()
124+
userID := "1"
125+
tenantLimits := newMockTenantLimits(map[string]*validation.Limits{userID: &limits})
126+
registry := prometheus.NewRegistry()
127+
128+
dir := t.TempDir()
129+
chunksDir := filepath.Join(dir, "chunks")
130+
blocksDir := filepath.Join(dir, "blocks")
131+
require.NoError(t, os.Mkdir(chunksDir, os.ModePerm))
132+
require.NoError(t, os.Mkdir(blocksDir, os.ModePerm))
133+
cfg := defaultIngesterTestConfig(t)
134+
cfg.MatchersCacheMaxItems = 50
135+
ing, err := prepareIngesterWithBlocksStorageAndLimits(t, cfg, limits, tenantLimits, blocksDir, registry, true)
136+
require.NoError(t, err)
137+
require.NoError(t, services.StartAndAwaitRunning(context.Background(), ing))
138+
139+
defer services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck
140+
141+
// Wait until it's ACTIVE
142+
test.Poll(t, time.Second, ring.ACTIVE, func() interface{} {
143+
return ing.lifecycler.GetState()
144+
})
145+
ctx := user.InjectOrgID(context.Background(), userID)
146+
// Lets have 1 key evicted
147+
numberOfDifferentMatchers := cfg.MatchersCacheMaxItems + 1
148+
callPerMatcher := 10
149+
for j := 0; j < numberOfDifferentMatchers; j++ {
150+
for i := 0; i < callPerMatcher; i++ {
151+
s := &mockQueryStreamServer{ctx: ctx}
152+
err = ing.QueryStream(&client.QueryRequest{
153+
StartTimestampMs: math.MinInt64,
154+
EndTimestampMs: math.MaxInt64,
155+
Matchers: []*client.LabelMatcher{{Type: client.EQUAL, Name: labels.MetricName, Value: fmt.Sprintf("%d", j)}},
156+
}, s)
157+
require.NoError(t, err)
158+
}
159+
}
160+
161+
require.NoError(t, testutil.GatherAndCompare(registry, bytes.NewBufferString(fmt.Sprintf(`
162+
# HELP ingester_matchers_cache_evicted_total Total number of items evicted from the cache
163+
# TYPE ingester_matchers_cache_evicted_total counter
164+
ingester_matchers_cache_evicted_total 1
165+
# HELP ingester_matchers_cache_hits_total Total number of cache hits for series matchers
166+
# TYPE ingester_matchers_cache_hits_total counter
167+
ingester_matchers_cache_hits_total %v
168+
# HELP ingester_matchers_cache_items Total number of cached items
169+
# TYPE ingester_matchers_cache_items gauge
170+
ingester_matchers_cache_items %v
171+
# HELP ingester_matchers_cache_max_items Maximum number of items that can be cached
172+
# TYPE ingester_matchers_cache_max_items gauge
173+
ingester_matchers_cache_max_items 0
174+
# HELP ingester_matchers_cache_requests_total Total number of cache requests for series matchers
175+
# TYPE ingester_matchers_cache_requests_total counter
176+
ingester_matchers_cache_requests_total %v
177+
`, callPerMatcher*numberOfDifferentMatchers-numberOfDifferentMatchers, cfg.MatchersCacheMaxItems, callPerMatcher*numberOfDifferentMatchers)), "ingester_matchers_cache_requests_total", "ingester_matchers_cache_hits_total", "ingester_matchers_cache_items", "ingester_matchers_cache_max_items", "ingester_matchers_cache_evicted_total"))
178+
}
179+
122180
func TestIngesterPerLabelsetLimitExceeded(t *testing.T) {
123181
limits := defaultLimitsTestConfig()
124182
userID := "1"

pkg/ingester/lifecycle_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func defaultIngesterTestConfig(t testing.TB) Config {
4343
cfg.LifecyclerConfig.FinalSleep = 0
4444
cfg.ActiveSeriesMetricsEnabled = true
4545
cfg.LabelsStringInterningEnabled = true
46+
cfg.MatchersCacheMaxItems = 1024
4647
return cfg
4748
}
4849

0 commit comments

Comments
 (0)