Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

query-frontend: Don't cache request with dedup=false #5300

Merged
merged 6 commits into from Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -12,6 +12,7 @@ We use *breaking :warning:* to mark changes that are not backward compatible (re

### Fixed
- [#5281](https://github.com/thanos-io/thanos/pull/5281) Blocks: Use correct separators for filesystem paths and object storage paths respectively.
- [#5300](https://github.com/thanos-io/thanos/pull/5300) Query: Ignore cache on queries with deduplication off.

### Added

Expand Down
6 changes: 6 additions & 0 deletions docs/components/query-frontend.md
Expand Up @@ -36,6 +36,12 @@ Query Frontend supports a retry mechanism to retry query when HTTP requests are

Query Frontend supports caching query results and reuses them on subsequent queries. If the cached results are incomplete, Query Frontend calculates the required subqueries and executes them in parallel on downstream queriers. Query Frontend can optionally align queries with their step parameter to improve the cacheability of the query results. Currently, in-memory cache (fifo cache) and memcached are supported.

### Excluded from caching

* Requests that support deduplication and having it disabled with `dedup=false`. Read more about deduplication in [Dedup documentation](query.md#deduplication-enabled).
* Requests that specify store matchers.
* Requests were the caching is explicitely disabled.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't we also excluding cache on bad requests?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, but that's kind of expected? Maybe it's worth to mention indeed 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Le't's fix in other PR

#### In-memory

```yaml mdox-exec="go run scripts/cfggen/main.go --name=queryfrontend.InMemoryResponseCacheConfig"
Expand Down
2 changes: 1 addition & 1 deletion pkg/queryfrontend/labels_codec_test.go
Expand Up @@ -28,7 +28,7 @@ func TestLabelsCodec_DecodeRequest(t *testing.T) {
url string
partialResponse bool
expectedError error
expectedRequest ThanosRequest
expectedRequest ThanosRequestStoreMatcherGetter
}{
{
name: "label_names cannot parse start",
Expand Down
2 changes: 1 addition & 1 deletion pkg/queryfrontend/queryrange_codec.go
Expand Up @@ -192,7 +192,7 @@ func parseDurationMillis(s string) (int64, error) {
}

func parseEnableDedupParam(s string) (bool, error) {
enableDeduplication := true
enableDeduplication := true // Deduplication is enabled by default.
if s != "" {
var err error
enableDeduplication, err = strconv.ParseBool(s)
Expand Down
32 changes: 23 additions & 9 deletions pkg/queryfrontend/request.go
Expand Up @@ -13,9 +13,9 @@ import (
"github.com/prometheus/prometheus/model/timestamp"
)

// TODO(yeya24): add partial result when needed.
// ThanosRequest is a common interface defined for specific thanos requests.
type ThanosRequest interface {
// ThanosRequestStoreMatcherGetter is a an interface for store matching that all request share.
// TODO(yeya24): Add partial result when needed.
type ThanosRequestStoreMatcherGetter interface {
GetStoreMatchers() [][]*labels.Matcher
}

Expand All @@ -24,6 +24,11 @@ type RequestHeader struct {
Values []string
}

// ThanosRequestDedup is a an interface for all requests that share setting deduplication.
type ThanosRequestDedup interface {
IsDedupEnabled() bool
}

type ThanosQueryRangeRequest struct {
Path string
Start int64
Expand All @@ -41,6 +46,12 @@ type ThanosQueryRangeRequest struct {
Headers []*RequestHeader
}

// IsDedupEnabled returns true if deduplication is enabled.
func (r *ThanosQueryRangeRequest) IsDedupEnabled() bool { return r.Dedup }

// GetStoreMatchers returns store matches.
func (r *ThanosQueryRangeRequest) GetStoreMatchers() [][]*labels.Matcher { return r.StoreMatchers }

// GetStart returns the start timestamp of the request in milliseconds.
func (r *ThanosQueryRangeRequest) GetStart() int64 { return r.Start }

Expand Down Expand Up @@ -102,8 +113,6 @@ func (r *ThanosQueryRangeRequest) String() string { return "" }
// which is not used in thanos.
func (r *ThanosQueryRangeRequest) ProtoMessage() {}

func (r *ThanosQueryRangeRequest) GetStoreMatchers() [][]*labels.Matcher { return r.StoreMatchers }

type ThanosLabelsRequest struct {
Start int64
End int64
Expand All @@ -116,6 +125,9 @@ type ThanosLabelsRequest struct {
Headers []*RequestHeader
}

// GetStoreMatchers returns store matches.
func (r *ThanosLabelsRequest) GetStoreMatchers() [][]*labels.Matcher { return r.StoreMatchers }

// GetStart returns the start timestamp of the request in milliseconds.
func (r *ThanosLabelsRequest) GetStart() int64 { return r.Start }

Expand Down Expand Up @@ -173,8 +185,6 @@ func (r *ThanosLabelsRequest) String() string { return "" }
// which is not used in thanos.
func (r *ThanosLabelsRequest) ProtoMessage() {}

func (r *ThanosLabelsRequest) GetStoreMatchers() [][]*labels.Matcher { return r.StoreMatchers }

type ThanosSeriesRequest struct {
Path string
Start int64
Expand All @@ -188,6 +198,12 @@ type ThanosSeriesRequest struct {
Headers []*RequestHeader
}

// IsDedupEnabled returns true if deduplication is enabled.
func (r *ThanosSeriesRequest) IsDedupEnabled() bool { return r.Dedup }

// GetStoreMatchers returns store matches.
func (r *ThanosSeriesRequest) GetStoreMatchers() [][]*labels.Matcher { return r.StoreMatchers }

// GetStart returns the start timestamp of the request in milliseconds.
func (r *ThanosSeriesRequest) GetStart() int64 { return r.Start }

Expand Down Expand Up @@ -243,5 +259,3 @@ func (r *ThanosSeriesRequest) String() string { return "" }
// ProtoMessage implements proto.Message interface required by queryrange.Request,
// which is not used in thanos.
func (r *ThanosSeriesRequest) ProtoMessage() {}

func (r *ThanosSeriesRequest) GetStoreMatchers() [][]*labels.Matcher { return r.StoreMatchers }
14 changes: 11 additions & 3 deletions pkg/queryfrontend/roundtrip.go
Expand Up @@ -274,10 +274,18 @@ func newLabelsTripperware(
}, nil
}

// Don't go to response cache if StoreMatchers are set.
// shouldCache controls what kind of Thanos request should be cached.
// For more information about requests that skip caching logic, please visit
// the query-frontend documentation.
func shouldCache(r queryrange.Request) bool {
if thanosReq, ok := r.(ThanosRequest); ok {
if len(thanosReq.GetStoreMatchers()) > 0 {
if thanosReqStoreMatcherGettable, ok := r.(ThanosRequestStoreMatcherGetter); ok {
if len(thanosReqStoreMatcherGettable.GetStoreMatchers()) > 0 {
return false
}
}

if thanosReqDedup, ok := r.(ThanosRequestDedup); ok {
if !thanosReqDedup.IsDedupEnabled() {
return false
}
}
Expand Down
73 changes: 50 additions & 23 deletions pkg/queryfrontend/roundtrip_test.go
Expand Up @@ -381,6 +381,16 @@ func TestRoundTripQueryRangeCacheMiddleware(t *testing.T) {
End: 2 * hour,
Step: 10 * seconds,
MaxSourceResolution: 1 * seconds,
Dedup: true, // Deduplication is enabled by default.
}

testRequestWithoutDedup := &ThanosQueryRangeRequest{
Path: "/api/v1/query_range",
Start: 0,
End: 2 * hour,
Step: 10 * seconds,
MaxSourceResolution: 1 * seconds,
Dedup: false,
}

// Non query range request, won't be cached.
Expand All @@ -389,6 +399,7 @@ func TestRoundTripQueryRangeCacheMiddleware(t *testing.T) {
Start: 0,
End: 2 * hour,
Step: 10 * seconds,
Dedup: true,
}

// Same query params as testRequest, different maxSourceResolution
Expand All @@ -399,6 +410,7 @@ func TestRoundTripQueryRangeCacheMiddleware(t *testing.T) {
End: 2 * hour,
Step: 10 * seconds,
MaxSourceResolution: 10 * seconds,
Dedup: true,
}

// Same query params as testRequest, different maxSourceResolution
Expand All @@ -409,6 +421,7 @@ func TestRoundTripQueryRangeCacheMiddleware(t *testing.T) {
End: 2 * hour,
Step: 10 * seconds,
MaxSourceResolution: 1 * hour,
Dedup: true,
}

// Same query params as testRequest, but with storeMatchers
Expand All @@ -419,6 +432,7 @@ func TestRoundTripQueryRangeCacheMiddleware(t *testing.T) {
Step: 10 * seconds,
MaxSourceResolution: 1 * seconds,
StoreMatchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}},
Dedup: true,
}

cacheConf := &queryrange.ResultsCacheConfig{
Expand Down Expand Up @@ -457,20 +471,22 @@ func TestRoundTripQueryRangeCacheMiddleware(t *testing.T) {
}{
{name: "first request", req: testRequest, expected: 1},
{name: "same request as the first one, directly use cache", req: testRequest, expected: 1},
{name: "non query range request won't be cached", req: testRequestInstant, expected: 2},
{name: "do it again", req: testRequestInstant, expected: 3},
{name: "different max source resolution but still same level", req: testRequestSameLevelDownsampling, expected: 3},
{name: "different max source resolution and different level", req: testRequestHigherLevelDownsampling, expected: 4},
{name: "storeMatchers requests won't go to cache", req: testRequestWithStoreMatchers, expected: 5},
{name: "same request as the first one but with dedup disabled, should not use cache", req: testRequestWithoutDedup, expected: 2},
{name: "non query range request won't be cached", req: testRequestInstant, expected: 3},
{name: "do it again", req: testRequestInstant, expected: 4},
{name: "different max source resolution but still same level", req: testRequestSameLevelDownsampling, expected: 4},
{name: "different max source resolution and different level", req: testRequestHigherLevelDownsampling, expected: 5},
{name: "storeMatchers requests won't go to cache", req: testRequestWithStoreMatchers, expected: 6},
{
name: "request but will be partitioned",
req: &ThanosQueryRangeRequest{
Path: "/api/v1/query_range",
Start: 0,
End: 25 * hour,
Step: 10 * seconds,
Dedup: true,
},
expected: 7,
expected: 8,
},
{
name: "same query as the previous one",
Expand All @@ -479,12 +495,12 @@ func TestRoundTripQueryRangeCacheMiddleware(t *testing.T) {
Start: 0,
End: 25 * hour,
Step: 10 * seconds,
Dedup: true,
},
expected: 7,
expected: 8,
},
} {

t.Run(tc.name, func(t *testing.T) {
if !t.Run(tc.name, func(t *testing.T) {

ctx := user.InjectOrgID(context.Background(), "1")
httpReq, err := NewThanosQueryRangeCodec(true).EncodeRequest(ctx, tc.req)
Expand All @@ -494,8 +510,9 @@ func TestRoundTripQueryRangeCacheMiddleware(t *testing.T) {
testutil.Ok(t, err)

testutil.Equals(t, tc.expected, *res)
})

}) {
break
}
}
}

Expand Down Expand Up @@ -608,9 +625,7 @@ func TestRoundTripLabelsCacheMiddleware(t *testing.T) {
expected: 8,
},
} {

t.Run(tc.name, func(t *testing.T) {

if !t.Run(tc.name, func(t *testing.T) {
ctx := user.InjectOrgID(context.Background(), "1")
httpReq, err := NewThanosLabelsCodec(true, 24*time.Hour).EncodeRequest(ctx, tc.req)
testutil.Ok(t, err)
Expand All @@ -619,8 +634,9 @@ func TestRoundTripLabelsCacheMiddleware(t *testing.T) {
testutil.Ok(t, err)

testutil.Equals(t, tc.expected, *res)
})

}) {
break
}
}
}

Expand All @@ -631,6 +647,15 @@ func TestRoundTripSeriesCacheMiddleware(t *testing.T) {
Start: 0,
End: 2 * hour,
Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}},
Dedup: true,
}

testRequestWithoutDedup := &ThanosSeriesRequest{
Path: "/api/v1/series",
Start: 0,
End: 2 * hour,
Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}},
Dedup: false,
}

// Different matchers set with the first request.
Expand All @@ -639,10 +664,11 @@ func TestRoundTripSeriesCacheMiddleware(t *testing.T) {
Start: 0,
End: 2 * hour,
Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "baz")}},
Dedup: true,
}

// Same query params as testRequest, but with storeMatchers
testRequestWithStoreMatchers := &ThanosLabelsRequest{
testRequestWithStoreMatchers := &ThanosSeriesRequest{
Path: "/api/v1/series",
Start: 0,
End: 2 * hour,
Expand Down Expand Up @@ -685,12 +711,12 @@ func TestRoundTripSeriesCacheMiddleware(t *testing.T) {
}{
{name: "first request", req: testRequest, expected: 1},
{name: "same request as the first one, directly use cache", req: testRequest, expected: 1},
{name: "different series request, not use cache", req: testRequest2, expected: 2},
{name: "storeMatchers requests won't go to cache", req: testRequestWithStoreMatchers, expected: 3},
{name: "same request as the first one but with dedup disabled, should not use cache", req: testRequestWithoutDedup, expected: 2},
{name: "different series request, not use cache", req: testRequest2, expected: 3},
{name: "storeMatchers requests won't go to cache", req: testRequestWithStoreMatchers, expected: 4},
} {

t.Run(tc.name, func(t *testing.T) {

if !t.Run(tc.name, func(t *testing.T) {
ctx := user.InjectOrgID(context.Background(), "1")
httpReq, err := NewThanosLabelsCodec(true, 24*time.Hour).EncodeRequest(ctx, tc.req)
testutil.Ok(t, err)
Expand All @@ -699,8 +725,9 @@ func TestRoundTripSeriesCacheMiddleware(t *testing.T) {
testutil.Ok(t, err)

testutil.Equals(t, tc.expected, *res)
})

}) {
break
}
}
}

Expand Down
20 changes: 15 additions & 5 deletions test/e2e/query_frontend_test.go
Expand Up @@ -109,7 +109,9 @@ func TestQueryFrontend(t *testing.T) {
timestamp.FromTime(now.Add(-time.Hour)),
timestamp.FromTime(now.Add(time.Hour)),
14,
promclient.QueryOptions{},
promclient.QueryOptions{
Deduplicate: true,
},
func(res model.Matrix) error {
if len(res) == 0 {
return errors.Errorf("expected some results, got nothing")
Expand Down Expand Up @@ -151,7 +153,9 @@ func TestQueryFrontend(t *testing.T) {
timestamp.FromTime(now.Add(-time.Hour)),
timestamp.FromTime(now.Add(time.Hour)),
14,
promclient.QueryOptions{},
promclient.QueryOptions{
Deduplicate: true,
},
func(res model.Matrix) error {
if len(res) == 0 {
return errors.Errorf("expected some results, got nothing")
Expand Down Expand Up @@ -196,7 +200,9 @@ func TestQueryFrontend(t *testing.T) {
timestamp.FromTime(now.Add(-time.Hour)),
timestamp.FromTime(now.Add(24*time.Hour)),
14,
promclient.QueryOptions{},
promclient.QueryOptions{
Deduplicate: true,
},
func(res model.Matrix) error {
if len(res) == 0 {
return errors.Errorf("expected some results, got nothing")
Expand Down Expand Up @@ -459,7 +465,9 @@ func TestQueryFrontendMemcachedCache(t *testing.T) {
timestamp.FromTime(now.Add(-time.Hour)),
timestamp.FromTime(now.Add(time.Hour)),
14,
promclient.QueryOptions{},
promclient.QueryOptions{
Deduplicate: true,
},
func(res model.Matrix) error {
if len(res) == 0 {
return errors.Errorf("expected some results, got nothing")
Expand Down Expand Up @@ -489,7 +497,9 @@ func TestQueryFrontendMemcachedCache(t *testing.T) {
timestamp.FromTime(now.Add(-time.Hour)),
timestamp.FromTime(now.Add(time.Hour)),
14,
promclient.QueryOptions{},
promclient.QueryOptions{
Deduplicate: true,
},
func(res model.Matrix) error {
if len(res) == 0 {
return errors.Errorf("expected some results, got nothing")
Expand Down