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 {