Skip to content
This repository has been archived by the owner on Aug 13, 2019. It is now read-only.

Optimize queries using regex matchers for set lookups #602

Merged
merged 19 commits into from
May 27, 2019
13 changes: 7 additions & 6 deletions labels/selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,15 @@ func NewEqualMatcher(name, value string) Matcher {
return &EqualMatcher{name: name, value: value}
}

type regexpMatcher struct {
type RegexpMatcher struct {
name string
re *regexp.Regexp
}

func (m regexpMatcher) Name() string { return m.name }
func (m regexpMatcher) Matches(v string) bool { return m.re.MatchString(v) }
func (m regexpMatcher) String() string { return fmt.Sprintf("%s=~%q", m.name, m.re.String()) }
func (m RegexpMatcher) Name() string { return m.name }
func (m RegexpMatcher) Matches(v string) bool { return m.re.MatchString(v) }
func (m RegexpMatcher) String() string { return fmt.Sprintf("%s=~%q", m.name, m.re.String()) }
func (m RegexpMatcher) Value() string { return m.re.String() }

// NewRegexpMatcher returns a new matcher verifying that a value matches
// the regular expression pattern.
Expand All @@ -79,7 +80,7 @@ func NewRegexpMatcher(name, pattern string) (Matcher, error) {
if err != nil {
return nil, err
}
return &regexpMatcher{name: name, re: re}, nil
return &RegexpMatcher{name: name, re: re}, nil
}

// NewMustRegexpMatcher returns a new matcher verifying that a value matches
Expand All @@ -90,7 +91,7 @@ func NewMustRegexpMatcher(name, pattern string) Matcher {
if err != nil {
panic(err)
}
return &regexpMatcher{name: name, re: re}
return &RegexpMatcher{name: name, re: re}

}

Expand Down
75 changes: 75 additions & 0 deletions querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"fmt"
"sort"
"strings"
"unicode/utf8"

"github.com/pkg/errors"
"github.com/prometheus/tsdb/chunkenc"
Expand Down Expand Up @@ -266,6 +267,62 @@ func (q *blockQuerier) Close() error {
return merr.Err()
}

// Bitmap used by func special to check whether a character needs to be escaped.
var specialBytes [16]byte

// special reports whether byte b needs to be escaped.
func special(b byte) bool {
naivewong marked this conversation as resolved.
Show resolved Hide resolved
return b < utf8.RuneSelf && specialBytes[b%16]&(1<<(b/16)) != 0
}

func init() {
for _, b := range []byte(`.+*?()|[]{}^$`) {
specialBytes[b%16] |= 1 << (b / 16)
}
}

func findSetMatches(pattern string) []string {
// Return empty matches if the wrapper from Prometheus is missing.
if len(pattern) < 6 || pattern[:4] != "^(?:" || pattern[len(pattern)-2:] != ")$" {
return []string{}
naivewong marked this conversation as resolved.
Show resolved Hide resolved
}
escaped := false
sets := []*strings.Builder{&strings.Builder{}}
for i := 4; i < len(pattern)-2; i++ {
if escaped {
// Add the escaped special character to the sets.
if special(pattern[i]) {
sets[len(sets)-1].WriteByte(pattern[i])
} else if pattern[i] == '\\' {
sets[len(sets)-1].WriteByte('\\')
} else {
return []string{}
}
escaped = false
} else {
// Return empty sets when there are special characters excluding '|'.
if special(pattern[i]) {
if pattern[i] == '|' {
sets = append(sets, &strings.Builder{})
} else {
return []string{}
}
} else if pattern[i] == '\\' {
escaped = true
} else {
sets[len(sets)-1].WriteByte(pattern[i])
}
naivewong marked this conversation as resolved.
Show resolved Hide resolved
}
}
matches := make([]string, 0, len(sets))
for _, s := range sets {
if s.Len() > 0 {
matches = append(matches, s.String())
}
}
return matches
}

// PostingsForMatchers assembles a single postings iterator against the index reader
// based on the given matchers.
func PostingsForMatchers(ix IndexReader, ms ...labels.Matcher) (index.Postings, error) {
Expand Down Expand Up @@ -346,6 +403,14 @@ func postingsForMatcher(ix IndexReader, m labels.Matcher) (index.Postings, error
return ix.Postings(em.Name(), em.Value())
}

// Fast-path for set matching.
if em, ok := m.(*labels.RegexpMatcher); ok {
setMatches := findSetMatches(em.Value())
if len(setMatches) > 0 {
return postingsForSetMatcher(ix, em.Name(), setMatches)
}
}

tpls, err := ix.LabelValues(m.Name())
if err != nil {
return nil, err
Expand Down Expand Up @@ -411,6 +476,16 @@ func inversePostingsForMatcher(ix IndexReader, m labels.Matcher) (index.Postings
return index.Merge(rit...), nil
}

func postingsForSetMatcher(ix IndexReader, name string, matches []string) (index.Postings, error) {
var its []index.Postings
for _, match := range matches {
if it, err := ix.Postings(name, match); err == nil {
naivewong marked this conversation as resolved.
Show resolved Hide resolved
its = append(its, it)
}
}
return index.Merge(its...), nil
}

func mergeStrings(a, b []string) []string {
maxl := len(a)
if len(b) > len(a) {
Expand Down
Loading