lib/storage: properly handle {__name__=~"prefix(suffix1|suffix2)",other_label="..."} queries

They were broken in the commit 00cbb099b6

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1644
This commit is contained in:
Aliaksandr Valialkin 2021-09-23 21:48:48 +03:00
parent f93146c81e
commit d15d036a5a
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
3 changed files with 89 additions and 4 deletions

View file

@ -9,6 +9,7 @@ sort: 15
* FEATURE: vmagent: add `vm_promscrape_max_scrape_size_exceeded_errors_total` metric for counting of the failed scrapes due to the exceeded response size (the response size limit can be configured via `-promscrape.maxScrapeSize` command-line flag). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1639).
* BUGFIX: vmalert: properly reload rule groups if only the `interval` config option is changed. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1641).
* BUGFIX: properly handle `{__name__=~"prefix(suffix1|suffix2)",other_label="..."}` queries. They may return unexpected empty responses since v1.66.0. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1644).
## [v1.66.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.66.1)

View file

@ -30,6 +30,7 @@ func convertToCompositeTagFilters(tfs *TagFilters) []*TagFilters {
var tfssCompiled []*TagFilters
// Search for filters on metric name, which will be used for creating composite filters.
var names [][]byte
namePrefix := ""
hasPositiveFilter := false
for _, tf := range tfs.tfs {
if len(tf.key) == 0 {
@ -43,6 +44,7 @@ func convertToCompositeTagFilters(tfs *TagFilters) []*TagFilters {
for _, orSuffix := range tf.orSuffixes {
names = append(names, []byte(orSuffix))
}
namePrefix = tf.regexpPrefix
}
} else if !tf.isNegative && !tf.isEmptyMatch {
hasPositiveFilter = true
@ -54,7 +56,7 @@ func convertToCompositeTagFilters(tfs *TagFilters) []*TagFilters {
}
// Create composite filters for the found names.
var compositeKey []byte
var compositeKey, nameWithPrefix []byte
for _, name := range names {
compositeFilters := 0
tfsNew := make([]tagFilter, 0, len(tfs.tfs))
@ -95,7 +97,9 @@ func convertToCompositeTagFilters(tfs *TagFilters) []*TagFilters {
continue
}
// Create composite filter on (name, tf)
compositeKey = marshalCompositeTagKey(compositeKey[:0], name, tf.key)
nameWithPrefix = append(nameWithPrefix[:0], namePrefix...)
nameWithPrefix = append(nameWithPrefix, name...)
compositeKey = marshalCompositeTagKey(compositeKey[:0], nameWithPrefix, tf.key)
var tfNew tagFilter
if err := tfNew.Init(tfs.commonPrefix, compositeKey, tf.value, tf.isNegative, tf.isRegexp); err != nil {
logger.Panicf("BUG: unexpected error when creating composite tag filter for name=%q and key=%q: %s", name, tf.key, err)
@ -239,13 +243,18 @@ type tagFilter struct {
// matchCost is a cost for matching a filter against a single string.
matchCost uint64
// contains the prefix for regexp filter if isRegexp==true.
regexpPrefix string
// Prefix always contains {nsPrefixTagToMetricIDs, AccountID, ProjectID, key}.
// Additionally it contains:
// - value if !isRegexp.
// - non-regexp prefix if isRegexp.
// - regexpPrefix if isRegexp.
prefix []byte
// or values obtained from regexp suffix if it equals to "foo|bar|..."
// `or` values obtained from regexp suffix if it equals to "foo|bar|..."
//
// the regexp prefix is stored in regexpPrefix.
//
// This array is also populated with matching Graphite metrics if key="__graphite__"
orSuffixes []string
@ -350,6 +359,7 @@ func (tf *tagFilter) InitFromGraphiteQuery(commonPrefix, query []byte, paths []s
tf.value = append(tf.value[:0], query...)
tf.isNegative = isNegative
tf.isRegexp = true // this is needed for tagFilter.matchSuffix
tf.regexpPrefix = prefix
tf.prefix = append(tf.prefix[:0], commonPrefix...)
tf.prefix = marshalTagValue(tf.prefix, nil)
tf.prefix = marshalTagValueNoTrailingTagSeparator(tf.prefix, []byte(prefix))
@ -395,6 +405,7 @@ func (tf *tagFilter) Init(commonPrefix, key, value []byte, isNegative, isRegexp
tf.isRegexp = isRegexp
tf.matchCost = 0
tf.regexpPrefix = ""
tf.prefix = tf.prefix[:0]
tf.orSuffixes = tf.orSuffixes[:0]
@ -412,6 +423,8 @@ func (tf *tagFilter) Init(commonPrefix, key, value []byte, isNegative, isRegexp
if len(expr) == 0 {
tf.value = append(tf.value[:0], prefix...)
tf.isRegexp = false
} else {
tf.regexpPrefix = string(prefix)
}
}
tf.prefix = marshalTagValueNoTrailingTagSeparator(tf.prefix, prefix)

View file

@ -418,6 +418,44 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
},
})
// Multiple values regexp filter, which can be converted to non-regexp.
f([]TagFilter{
{
Key: nil,
Value: []byte("bar|foo"),
IsNegative: false,
IsRegexp: true,
},
}, [][]TagFilter{
{
{
Key: nil,
Value: []byte("bar|foo"),
IsNegative: false,
IsRegexp: true,
},
},
})
// Multiple values regexp filter with common prefix, which can be converted to non-regexp.
f([]TagFilter{
{
Key: nil,
Value: []byte("xxx(bar|foo)"),
IsNegative: false,
IsRegexp: true,
},
}, [][]TagFilter{
{
{
Key: nil,
Value: []byte("xxx(bar|foo)"),
IsNegative: false,
IsRegexp: true,
},
},
})
// Multiple values regexp filter, which can be converted to non-regexp, with non-name filter.
f([]TagFilter{
{
@ -451,6 +489,39 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
},
})
// Multiple values regexp filter with common prefix, which can be converted to non-regexp, with non-name filter.
f([]TagFilter{
{
Key: nil,
Value: []byte("xxx(bar|foox)"),
IsNegative: false,
IsRegexp: true,
},
{
Key: []byte("foo"),
Value: []byte("abc"),
IsNegative: false,
IsRegexp: false,
},
}, [][]TagFilter{
{
{
Key: []byte("\xfe\x06xxxbarfoo"),
Value: []byte("abc"),
IsNegative: false,
IsRegexp: false,
},
},
{
{
Key: []byte("\xfe\x07xxxfooxfoo"),
Value: []byte("abc"),
IsNegative: false,
IsRegexp: false,
},
},
})
// Two multiple values regexp filter, which can be converted to non-regexp, with non-name filter.
f([]TagFilter{
{