From ed172a666737a11a09e56d512078b548fe9edb18 Mon Sep 17 00:00:00 2001 From: Oleg Zaytsev Date: Tue, 23 Jan 2024 11:40:21 +0100 Subject: [PATCH] Optimize label values with matchers by taking shortcuts (#13426) Don't calculate postings beforehand: we may not need them. If all matchers are for the requested label, we can just filter its values. Also, if there are no values at all, no need to run any kind of logic. Also add more labelValuesWithMatchers benchmarks Signed-off-by: Oleg Zaytsev --- tsdb/ooo_head_read_test.go | 4 ++-- tsdb/querier.go | 21 ++++++++++++++++----- tsdb/querier_bench_test.go | 13 ++++++++++++- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/tsdb/ooo_head_read_test.go b/tsdb/ooo_head_read_test.go index b50a268b71..36289056b8 100644 --- a/tsdb/ooo_head_read_test.go +++ b/tsdb/ooo_head_read_test.go @@ -410,7 +410,7 @@ func TestOOOHeadChunkReader_LabelValues(t *testing.T) { queryMinT: math.MinInt64, queryMaxT: math.MaxInt64, expValues1: []string{"bar1"}, - expValues2: []string{}, + expValues2: nil, expValues3: []string{"bar1", "bar2"}, expValues4: []string{"bar1", "bar2"}, }, @@ -419,7 +419,7 @@ func TestOOOHeadChunkReader_LabelValues(t *testing.T) { queryMinT: 90, queryMaxT: 90, expValues1: []string{"bar1"}, - expValues2: []string{}, + expValues2: nil, expValues3: []string{"bar1", "bar2"}, expValues4: []string{"bar1", "bar2"}, }, diff --git a/tsdb/querier.go b/tsdb/querier.go index f88e4415e4..68aa957463 100644 --- a/tsdb/querier.go +++ b/tsdb/querier.go @@ -440,11 +440,6 @@ func inversePostingsForMatcher(ctx context.Context, ix IndexReader, m *labels.Ma } func labelValuesWithMatchers(ctx context.Context, r IndexReader, name string, matchers ...*labels.Matcher) ([]string, error) { - p, err := PostingsForMatchers(ctx, r, matchers...) - if err != nil { - return nil, fmt.Errorf("fetching postings for matchers: %w", err) - } - allValues, err := r.LabelValues(ctx, name) if err != nil { return nil, fmt.Errorf("fetching values of label %s: %w", name, err) @@ -453,8 +448,10 @@ func labelValuesWithMatchers(ctx context.Context, r IndexReader, name string, ma // If we have a matcher for the label name, we can filter out values that don't match // before we fetch postings. This is especially useful for labels with many values. // e.g. __name__ with a selector like {__name__="xyz"} + hasMatchersForOtherLabels := false for _, m := range matchers { if m.Name != name { + hasMatchersForOtherLabels = true continue } @@ -469,6 +466,20 @@ func labelValuesWithMatchers(ctx context.Context, r IndexReader, name string, ma allValues = filteredValues } + if len(allValues) == 0 { + return nil, nil + } + + // If we don't have any matchers for other labels, then we're done. + if !hasMatchersForOtherLabels { + return allValues, nil + } + + p, err := PostingsForMatchers(ctx, r, matchers...) + if err != nil { + return nil, fmt.Errorf("fetching postings for matchers: %w", err) + } + valuesPostings := make([]index.Postings, len(allValues)) for i, value := range allValues { valuesPostings[i], err = r.Postings(ctx, name, value) diff --git a/tsdb/querier_bench_test.go b/tsdb/querier_bench_test.go index 7b839ca348..9a82302420 100644 --- a/tsdb/querier_bench_test.go +++ b/tsdb/querier_bench_test.go @@ -182,13 +182,16 @@ func benchmarkPostingsForMatchers(b *testing.B, ir IndexReader) { func benchmarkLabelValuesWithMatchers(b *testing.B, ir IndexReader) { i1 := labels.MustNewMatcher(labels.MatchEqual, "i", "1") + i1Plus := labels.MustNewMatcher(labels.MatchRegexp, "i", "1.+") + i1PostingsBenchSuffix := labels.MustNewMatcher(labels.MatchEqual, "i", "1"+postingsBenchSuffix) + iSuffix := labels.MustNewMatcher(labels.MatchRegexp, "i", ".+ddd") iStar := labels.MustNewMatcher(labels.MatchRegexp, "i", "^.*$") jNotFoo := labels.MustNewMatcher(labels.MatchNotEqual, "j", "foo") jXXXYYY := labels.MustNewMatcher(labels.MatchRegexp, "j", "XXX|YYY") jXplus := labels.MustNewMatcher(labels.MatchRegexp, "j", "X.+") n1 := labels.MustNewMatcher(labels.MatchEqual, "n", "1"+postingsBenchSuffix) nX := labels.MustNewMatcher(labels.MatchNotEqual, "n", "X"+postingsBenchSuffix) - nPlus := labels.MustNewMatcher(labels.MatchRegexp, "i", "^.+$") + nPlus := labels.MustNewMatcher(labels.MatchRegexp, "n", "^.+$") ctx := context.Background() @@ -197,6 +200,7 @@ func benchmarkLabelValuesWithMatchers(b *testing.B, ir IndexReader) { labelName string matchers []*labels.Matcher }{ + // i is never "1", this isn't matching anything. {`i with i="1"`, "i", []*labels.Matcher{i1}}, // i has 100k values. {`i with n="1"`, "i", []*labels.Matcher{n1}}, @@ -206,9 +210,16 @@ func benchmarkLabelValuesWithMatchers(b *testing.B, ir IndexReader) { {`i with n="1",j=~"XXX|YYY"`, "i", []*labels.Matcher{n1, jXXXYYY}}, {`i with n="X",j!="foo"`, "i", []*labels.Matcher{nX, jNotFoo}}, {`i with n="1",i=~"^.*$",j!="foo"`, "i", []*labels.Matcher{n1, iStar, jNotFoo}}, + // matchers on i itself + {`i with i="1aaa...ddd"`, "i", []*labels.Matcher{i1PostingsBenchSuffix}}, + {`i with i=~"1.+"`, "i", []*labels.Matcher{i1Plus}}, + {`i with i=~"1.+",i=~".+ddd"`, "i", []*labels.Matcher{i1Plus, iSuffix}}, // n has 10 values. {`n with j!="foo"`, "n", []*labels.Matcher{jNotFoo}}, {`n with i="1"`, "n", []*labels.Matcher{i1}}, + {`n with i=~"1.+"`, "n", []*labels.Matcher{i1Plus}}, + // there's no label "none" + {`none with i=~"1"`, "none", []*labels.Matcher{i1Plus}}, } for _, c := range cases {