Skip to content

Commit 1c76409

Browse files
authored
Add per-tenant max cache freshness to query-frontend (#2609)
* Add per-tenant MaxCacheFreshness parameter Signed-off-by: Annanay <[email protected]> * Simplify test Signed-off-by: Annanay <[email protected]> * Update docs, CHANGELOG Signed-off-by: Annanay <[email protected]> * Address reviews, fix code comments Signed-off-by: Annanay <[email protected]> * Address comments Signed-off-by: Annanay <[email protected]> * Update CHANGELOG Signed-off-by: Annanay <[email protected]> * Fix failing test Signed-off-by: Annanay <[email protected]> * Address comments from @pracucci Signed-off-by: Annanay <[email protected]>
1 parent a92b613 commit 1c76409

File tree

6 files changed

+100
-14
lines changed

6 files changed

+100
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* [CHANGE] Query Frontend now uses Round Robin to choose a tenant queue to service next. #2553
66
* [CHANGE] `-promql.lookback-delta` is now deprecated and has been replaced by `-querier.lookback-delta` along with `lookback_delta` entry under `querier` in the config file. `-promql.lookback-delta` will be removed in v1.4.0. #2604
77
* [FEATURE] TLS config options added for GRPC clients in Querier (Query-frontend client & Ingester client), Ruler, Store Gateway, as well as HTTP client in Config store client. #2502
8+
* [FEATURE] The flag `-frontend.max-cache-freshness` is now supported within the limits overrides, to specify per-tenant max cache freshness values. The corresponding YAML config parameter has been changed from `results_cache.max_freshness` to `limits_config.max_cache_freshness`. The legacy YAML config parameter (`results_cache.max_freshness`) will continue to be supported till Cortex release `v1.4.0`. #2609
89
* [ENHANCEMENT] Experimental TSDB: added the following metrics to the ingester: #2580 #2583 #2589
910
* `cortex_ingester_tsdb_appender_add_duration_seconds`
1011
* `cortex_ingester_tsdb_appender_commit_duration_seconds`

docs/configuration/config-file-reference.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -759,11 +759,6 @@ results_cache:
759759
# The CLI flags prefix for this block config is: frontend
760760
[fifocache: <fifo_cache_config>]
761761
762-
# Most recent allowed cacheable result, to prevent caching very recent results
763-
# that might still be in flux.
764-
# CLI flag: -frontend.max-cache-freshness
765-
[max_freshness: <duration> | default = 1m]
766-
767762
# Cache query results.
768763
# CLI flag: -querier.cache-results
769764
[cache_results: <boolean> | default = false]
@@ -2384,6 +2379,11 @@ The `limits_config` configures default and per-tenant limits imposed by Cortex s
23842379
# CLI flag: -store.cardinality-limit
23852380
[cardinality_limit: <int> | default = 100000]
23862381
2382+
# Most recent allowed cacheable result per-tenant, to prevent caching very
2383+
# recent results that might still be in flux.
2384+
# CLI flag: -frontend.max-cache-freshness
2385+
[max_cache_freshness: <duration> | default = 1m]
2386+
23872387
# File name of per-user overrides. [deprecated, use -runtime-config.file
23882388
# instead]
23892389
# CLI flag: -limits.per-user-override-config

pkg/querier/queryrange/limits.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
type Limits interface {
1818
MaxQueryLength(string) time.Duration
1919
MaxQueryParallelism(string) int
20+
MaxCacheFreshness(string) time.Duration
2021
}
2122

2223
type limits struct {

pkg/querier/queryrange/results_cache.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,15 @@ type CacheGenNumberLoader interface {
3939

4040
// ResultsCacheConfig is the config for the results cache.
4141
type ResultsCacheConfig struct {
42-
CacheConfig cache.Config `yaml:"cache"`
43-
MaxCacheFreshness time.Duration `yaml:"max_freshness"`
42+
CacheConfig cache.Config `yaml:"cache"`
43+
LegacyMaxCacheFreshness time.Duration `yaml:"max_freshness" doc:"hidden"` // TODO: (deprecated) remove in Cortex v1.4.0
4444
}
4545

4646
// RegisterFlags registers flags.
4747
func (cfg *ResultsCacheConfig) RegisterFlags(f *flag.FlagSet) {
4848
cfg.CacheConfig.RegisterFlagsWithPrefix("frontend.", "", f)
4949

5050
flagext.DeprecatedFlag(f, "frontend.cache-split-interval", "Deprecated: The maximum interval expected for each request, results will be cached per single interval. This behavior is now determined by querier.split-queries-by-interval.")
51-
52-
f.DurationVar(&cfg.MaxCacheFreshness, "frontend.max-cache-freshness", 1*time.Minute, "Most recent allowed cacheable result, to prevent caching very recent results that might still be in flux.")
5351
}
5452

5553
// Extractor is used by the cache to extract a subset of a response from a cache entry.
@@ -171,7 +169,12 @@ func (s resultsCache) Do(ctx context.Context, r Request) (Response, error) {
171169
response Response
172170
)
173171

174-
maxCacheTime := int64(model.Now().Add(-s.cfg.MaxCacheFreshness))
172+
// check if cache freshness value is provided in legacy config
173+
maxCacheFreshness := s.cfg.LegacyMaxCacheFreshness
174+
if maxCacheFreshness == time.Duration(0) {
175+
maxCacheFreshness = s.limits.MaxCacheFreshness(userID)
176+
}
177+
maxCacheTime := int64(model.Now().Add(-maxCacheFreshness))
175178
if r.GetStart() > maxCacheTime {
176179
return s.next.Do(ctx, r)
177180
}
@@ -184,7 +187,7 @@ func (s resultsCache) Do(ctx context.Context, r Request) (Response, error) {
184187
}
185188

186189
if err == nil && len(extents) > 0 {
187-
extents, err := s.filterRecentExtents(r, extents)
190+
extents, err := s.filterRecentExtents(r, maxCacheFreshness, extents)
188191
if err != nil {
189192
return nil, err
190193
}
@@ -417,8 +420,8 @@ func partition(req Request, extents []Extent, extractor Extractor) ([]Request, [
417420
return requests, cachedResponses, nil
418421
}
419422

420-
func (s resultsCache) filterRecentExtents(req Request, extents []Extent) ([]Extent, error) {
421-
maxCacheTime := (int64(model.Now().Add(-s.cfg.MaxCacheFreshness)) / req.GetStep()) * req.GetStep()
423+
func (s resultsCache) filterRecentExtents(req Request, maxCacheFreshness time.Duration, extents []Extent) ([]Extent, error) {
424+
maxCacheTime := (int64(model.Now().Add(-maxCacheFreshness)) / req.GetStep()) * req.GetStep()
422425
for i := range extents {
423426
// Never cache data for the latest freshness period.
424427
if extents[i].End > maxCacheTime {

pkg/querier/queryrange/results_cache_test.go

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,18 @@ func (fakeLimits) MaxQueryParallelism(string) int {
352352
return 14 // Flag default.
353353
}
354354

355+
func (fakeLimits) MaxCacheFreshness(string) time.Duration {
356+
return time.Duration(0)
357+
}
358+
359+
type fakeLimitsHighMaxCacheFreshness struct {
360+
fakeLimits
361+
}
362+
363+
func (fakeLimitsHighMaxCacheFreshness) MaxCacheFreshness(string) time.Duration {
364+
return 10 * time.Minute
365+
}
366+
355367
func TestResultsCache(t *testing.T) {
356368
calls := 0
357369
cfg := ResultsCacheConfig{
@@ -397,7 +409,7 @@ func TestResultsCacheRecent(t *testing.T) {
397409
var cfg ResultsCacheConfig
398410
flagext.DefaultValues(&cfg)
399411
cfg.CacheConfig.Cache = cache.NewMockCache()
400-
rcm, _, err := NewResultsCacheMiddleware(log.NewNopLogger(), cfg, constSplitter(day), fakeLimits{}, PrometheusCodec, PrometheusResponseExtractor{}, nil)
412+
rcm, _, err := NewResultsCacheMiddleware(log.NewNopLogger(), cfg, constSplitter(day), fakeLimitsHighMaxCacheFreshness{}, PrometheusCodec, PrometheusResponseExtractor{}, nil)
401413
require.NoError(t, err)
402414

403415
req := parsedRequest.WithStartEnd(int64(model.Now())-(60*1e3), int64(model.Now()))
@@ -423,6 +435,68 @@ func TestResultsCacheRecent(t *testing.T) {
423435
require.Equal(t, parsedResponse, resp)
424436
}
425437

438+
func TestResultsCacheMaxFreshness(t *testing.T) {
439+
modelNow := model.Now()
440+
for i, tc := range []struct {
441+
legacyMaxCacheFreshness time.Duration
442+
fakeLimits Limits
443+
Handler HandlerFunc
444+
expectedResponse *PrometheusResponse
445+
}{
446+
{
447+
// should lookup cache because legacy cache max freshness will be applied
448+
legacyMaxCacheFreshness: 5 * time.Second,
449+
fakeLimits: fakeLimits{},
450+
Handler: nil,
451+
expectedResponse: mkAPIResponse(int64(modelNow)-(50*1e3), int64(modelNow)-(10*1e3), 10),
452+
},
453+
{
454+
// should not lookup cache because per-tenant override will be applied
455+
legacyMaxCacheFreshness: time.Duration(0),
456+
fakeLimits: fakeLimitsHighMaxCacheFreshness{},
457+
Handler: HandlerFunc(func(_ context.Context, _ Request) (Response, error) {
458+
return parsedResponse, nil
459+
}),
460+
expectedResponse: parsedResponse,
461+
},
462+
} {
463+
t.Run(strconv.Itoa(i), func(t *testing.T) {
464+
var cfg ResultsCacheConfig
465+
flagext.DefaultValues(&cfg)
466+
cfg.CacheConfig.Cache = cache.NewMockCache()
467+
468+
cfg.LegacyMaxCacheFreshness = tc.legacyMaxCacheFreshness
469+
470+
fakeLimits := tc.fakeLimits
471+
rcm, _, err := NewResultsCacheMiddleware(
472+
log.NewNopLogger(),
473+
cfg,
474+
constSplitter(day),
475+
fakeLimits,
476+
PrometheusCodec,
477+
PrometheusResponseExtractor{},
478+
nil,
479+
)
480+
require.NoError(t, err)
481+
482+
// create cache with handler
483+
rc := rcm.Wrap(tc.Handler)
484+
ctx := user.InjectOrgID(context.Background(), "1")
485+
486+
// create request with start end within the key extents
487+
req := parsedRequest.WithStartEnd(int64(modelNow)-(50*1e3), int64(modelNow)-(10*1e3))
488+
489+
// fill cache
490+
key := constSplitter(day).GenerateCacheKey("1", req)
491+
rc.(*resultsCache).put(ctx, key, []Extent{mkExtent(int64(modelNow)-(60*1e3), int64(modelNow))})
492+
493+
resp, err := rc.Do(ctx, req)
494+
require.NoError(t, err)
495+
require.Equal(t, tc.expectedResponse, resp)
496+
})
497+
}
498+
}
499+
426500
func Test_resultsCache_MissingData(t *testing.T) {
427501
cfg := ResultsCacheConfig{
428502
CacheConfig: cache.Config{

pkg/util/validation/limits.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ type Limits struct {
6060
MaxQueryLength time.Duration `yaml:"max_query_length"`
6161
MaxQueryParallelism int `yaml:"max_query_parallelism"`
6262
CardinalityLimit int `yaml:"cardinality_limit"`
63+
MaxCacheFreshness time.Duration `yaml:"max_cache_freshness"`
6364

6465
// Config for overrides, convenient if it goes here. [Deprecated in favor of RuntimeConfig flag in cortex.Config]
6566
PerTenantOverrideConfig string `yaml:"per_tenant_override_config"`
@@ -103,6 +104,7 @@ func (l *Limits) RegisterFlags(f *flag.FlagSet) {
103104
f.DurationVar(&l.MaxQueryLength, "store.max-query-length", 0, "Limit to length of chunk store queries, 0 to disable.")
104105
f.IntVar(&l.MaxQueryParallelism, "querier.max-query-parallelism", 14, "Maximum number of queries will be scheduled in parallel by the frontend.")
105106
f.IntVar(&l.CardinalityLimit, "store.cardinality-limit", 1e5, "Cardinality limit for index queries.")
107+
f.DurationVar(&l.MaxCacheFreshness, "frontend.max-cache-freshness", 1*time.Minute, "Most recent allowed cacheable result per-tenant, to prevent caching very recent results that might still be in flux.")
106108

107109
f.StringVar(&l.PerTenantOverrideConfig, "limits.per-user-override-config", "", "File name of per-user overrides. [deprecated, use -runtime-config.file instead]")
108110
f.DurationVar(&l.PerTenantOverridePeriod, "limits.per-user-override-period", 10*time.Second, "Period with which to reload the overrides. [deprecated, use -runtime-config.reload-period instead]")
@@ -282,6 +284,11 @@ func (o *Overrides) MaxQueryLength(userID string) time.Duration {
282284
return o.getOverridesForUser(userID).MaxQueryLength
283285
}
284286

287+
// MaxCacheFreshness returns the limit of the length (in time) of a query.
288+
func (o *Overrides) MaxCacheFreshness(userID string) time.Duration {
289+
return o.getOverridesForUser(userID).MaxCacheFreshness
290+
}
291+
285292
// MaxQueryParallelism returns the limit to the number of sub-queries the
286293
// frontend will process in parallel.
287294
func (o *Overrides) MaxQueryParallelism(userID string) int {

0 commit comments

Comments
 (0)