From 6d6b81aaa78e868a2cb032760544ba2fc2b10bc3 Mon Sep 17 00:00:00 2001 From: Ben Ye Date: Sun, 23 Feb 2025 12:11:39 -0800 Subject: [PATCH] Adjust max_source_resolution automatically based promql queries Signed-off-by: Ben Ye --- CHANGELOG.md | 2 +- pkg/query/querier.go | 25 +++++++- pkg/query/querier_test.go | 132 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b18e7039fe..ef6dbfa1ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,6 @@ We use *breaking :warning:* to mark changes that are not backward compatible (re ### Fixed - [#8091](https://github.com/thanos-io/thanos/pull/8091) *: Add POST into allowed CORS methods header - [#8046](https://github.com/thanos-io/thanos/pull/8046) Query-Frontend: Fix query statistic reporting for range queries when caching is enabled. - - [#7978](https://github.com/thanos-io/thanos/pull/7978) Receive: Fix deadlock during local writes when `split-tenant-label-name` is used - [#8016](https://github.com/thanos-io/thanos/pull/8016) Query Frontend: Fix @ modifier not being applied correctly on sub queries. @@ -28,6 +27,7 @@ We use *breaking :warning:* to mark changes that are not backward compatible (re ### Changed - [#7890](https://github.com/thanos-io/thanos/pull/7890) Query,Ruler: *breaking :warning:* deprecated `--store.sd-file` and `--store.sd-interval` to be replaced with `--endpoint.sd-config` and `--endpoint-sd-config-reload-interval`; removed legacy flags to pass endpoints `--store`, `--metadata`, `--rule`, `--exemplar`. +- [#7012](https://github.com/thanos-io/thanos/pull/7012) Query: Automatically adjust `max_source_resolution` based on promql query to avoid querying data from higher resolution resulting empty results. ### Removed diff --git a/pkg/query/querier.go b/pkg/query/querier.go index e084344ed9..0abe0f2e71 100644 --- a/pkg/query/querier.go +++ b/pkg/query/querier.go @@ -27,6 +27,18 @@ import ( "github.com/thanos-io/thanos/pkg/tracing" ) +var promqlFuncRequiresTwoSamples = map[string]struct{}{ + "rate": {}, + "irate": {}, + "increase": {}, + "delta": {}, + "idelta": {}, + "deriv": {}, + "predict_linear": {}, + "holt_winters": {}, + "double_exponential_smoothing": {}, +} + type seriesStatsReporter func(seriesStats storepb.SeriesStatsCounter) var NoopSeriesStatsReporter seriesStatsReporter = func(_ storepb.SeriesStatsCounter) {} @@ -320,6 +332,7 @@ func (q *querier) selectFn(ctx context.Context, hints *storage.SelectHints, ms . } aggrs := aggrsFromFunc(hints.Func) + maxResolutionMillis := maxResolutionFromSelectHints(q.maxResolutionMillis, *hints) // TODO(bwplotka): Pass it using the SeriesRequest instead of relying on context. ctx = context.WithValue(ctx, store.StoreMatcherKey, q.storeDebugMatchers) @@ -333,7 +346,7 @@ func (q *querier) selectFn(ctx context.Context, hints *storage.SelectHints, ms . MaxTime: hints.End, Limit: int64(hints.Limit), Matchers: sms, - MaxResolutionWindow: q.maxResolutionMillis, + MaxResolutionWindow: maxResolutionMillis, Aggregates: aggrs, ShardInfo: q.shardInfo, PartialResponseStrategy: q.partialResponseStrategy, @@ -460,3 +473,13 @@ func (q *querier) LabelNames(ctx context.Context, hints *storage.LabelHints, mat } func (q *querier) Close() error { return nil } + +// maxResolutionFromSelectHints finds the max possible resolution by inferring from the promql query. +func maxResolutionFromSelectHints(maxResolutionMillis int64, hints storage.SelectHints) int64 { + if hints.Range > 0 { + if _, ok := promqlFuncRequiresTwoSamples[hints.Func]; ok { + maxResolutionMillis = min(maxResolutionMillis, hints.Range/2) + } + } + return maxResolutionMillis +} diff --git a/pkg/query/querier_test.go b/pkg/query/querier_test.go index 7b3df0fee8..7ef76dc71f 100644 --- a/pkg/query/querier_test.go +++ b/pkg/query/querier_test.go @@ -32,6 +32,7 @@ import ( "github.com/prometheus/prometheus/util/gate" "github.com/thanos-io/thanos/pkg/logutil" + "github.com/thanos-io/thanos/pkg/compact/downsample" "github.com/thanos-io/thanos/pkg/component" "github.com/thanos-io/thanos/pkg/store" "github.com/thanos-io/thanos/pkg/store/labelpb" @@ -1279,3 +1280,134 @@ func storeSeriesResponse(t testing.TB, lset labels.Labels, smplChunks ...[]sampl } return storepb.NewSeriesResponse(&s) } + +func TestMaxResolutionFromSelectHints(t *testing.T) { + twoMinRange := int64(2 * 60 * 1000) + for _, tc := range []struct { + name string + maxResolutionMillis int64 + hints storage.SelectHints + expected int64 + }{ + { + name: "no range", + hints: storage.SelectHints{ + Range: 0, + }, + maxResolutionMillis: downsample.ResLevel1, + expected: downsample.ResLevel1, + }, + { + name: "no function", + hints: storage.SelectHints{ + Range: twoMinRange, + }, + maxResolutionMillis: downsample.ResLevel1, + expected: downsample.ResLevel1, + }, + { + name: "function doesn't impact resolution", + hints: storage.SelectHints{ + Range: twoMinRange, + Func: "max_over_time", + }, + maxResolutionMillis: downsample.ResLevel1, + expected: downsample.ResLevel1, + }, + { + name: "rate", + hints: storage.SelectHints{ + Range: twoMinRange, + Func: "rate", + }, + maxResolutionMillis: downsample.ResLevel1, + expected: twoMinRange / 2, + }, + { + name: "rate", + hints: storage.SelectHints{ + Range: twoMinRange, + Func: "rate", + }, + maxResolutionMillis: downsample.ResLevel1, + expected: twoMinRange / 2, + }, + { + name: "irate", + hints: storage.SelectHints{ + Range: twoMinRange, + Func: "irate", + }, + maxResolutionMillis: downsample.ResLevel1, + expected: twoMinRange / 2, + }, + { + name: "increase", + hints: storage.SelectHints{ + Range: twoMinRange, + Func: "increase", + }, + maxResolutionMillis: downsample.ResLevel1, + expected: twoMinRange / 2, + }, + { + name: "delta", + hints: storage.SelectHints{ + Range: twoMinRange, + Func: "delta", + }, + maxResolutionMillis: downsample.ResLevel1, + expected: twoMinRange / 2, + }, + { + name: "idelta", + hints: storage.SelectHints{ + Range: twoMinRange, + Func: "idelta", + }, + maxResolutionMillis: downsample.ResLevel1, + expected: twoMinRange / 2, + }, + { + name: "deriv", + hints: storage.SelectHints{ + Range: twoMinRange, + Func: "deriv", + }, + maxResolutionMillis: downsample.ResLevel1, + expected: twoMinRange / 2, + }, + { + name: "predict_linear", + hints: storage.SelectHints{ + Range: twoMinRange, + Func: "predict_linear", + }, + maxResolutionMillis: downsample.ResLevel1, + expected: twoMinRange / 2, + }, + { + name: "holt_winters", + hints: storage.SelectHints{ + Range: twoMinRange, + Func: "holt_winters", + }, + maxResolutionMillis: downsample.ResLevel1, + expected: twoMinRange / 2, + }, + { + name: "double_exponential_smoothing", + hints: storage.SelectHints{ + Range: twoMinRange, + Func: "double_exponential_smoothing", + }, + maxResolutionMillis: downsample.ResLevel1, + expected: twoMinRange / 2, + }, + } { + t.Run(tc.name, func(t *testing.T) { + res := maxResolutionFromSelectHints(tc.maxResolutionMillis, tc.hints) + testutil.Equals(t, tc.expected, res) + }) + } +}