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 <mail@olegzaytsev.com>
This commit is contained in:
Oleg Zaytsev 2024-01-23 11:40:21 +01:00 committed by GitHub
parent c095ba24f2
commit ed172a6667
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 30 additions and 8 deletions

View File

@ -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"},
},

View File

@ -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)

View File

@ -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 {