Repository: VictoriaMetrics/VictoriaMetrics
Pull request #1616 (2021-09-14 09:42:08)

Remove port 3000 and add https to play-grafana link


Patch:
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index b7fe9a910..ae8af1f5a 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -19,6 +19,7 @@ sort: 15
 * FEATURE: vmagent: reduce CPU usage when applying `series_limit` to scrape targets with constant set of metrics. See more information about `series_limit` [here](https://docs.victoriametrics.com/vmagent.html#cardinality-limiter).
 * FEATURE: vminsert: disable rerouting by default when a few of `vmstorage` nodes start accepting data at lower speed than the rest of `vmstorage` nodes. This should improve VictoriaMetrics cluster stability during rolling restarts and during spikes in [time series churn rate](https://docs.victoriametrics.com/FAQ.html#what-is-high-churn-rate). The rerouting can be enabled by passing `-disableRerouting=false` command-line flag to `vminsert`.
 * FEATURE: vmauth: do not put invalid auth tokens into log by default due to security reasons. The logging can be returned back by passing `-logInvalidAuthTokens` command-line flag to `vmauth`. Requests with invalid auth tokens are counted at `vmagent_http_request_errors_total{reason="invalid_auth_token"}` metric exposed by `vmauth` at `/metrics` page.
+* FEATURE: optimize performance for queries with regexp filters on metric name like `{__name__=~"metric1|...|metricN"}`. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/1610) from @faceair.
 
 * BUGFIX: properly handle queries with multiple filters matching empty labels such as `metric{label1=~"foo|",label2="bar|"}`. This filter must match the following series: `metric`, `metric{label1="foo"}`, `metric{label2="bar"}` and `metric{label1="foo",label2="bar"}`. Previously it was matching only `metric{label1="foo",label2="bar"}`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1601).
 * BUGFIX: vmselect: reset connection timeouts after each request to `vmstorage`. This should prevent from `cannot read data in 0.000 seconds: unexpected EOF` warning in logs. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1562). Thanks to @mxlxm .
diff --git a/docs/MetricsQL.md b/docs/MetricsQL.md
index 3560fa7cd..711ac0ff2 100644
--- a/docs/MetricsQL.md
+++ b/docs/MetricsQL.md
@@ -24,7 +24,7 @@ Other PromQL functionality should work the same in MetricsQL. [File an issue](ht
 
 MetricsQL implements [PromQL](https://medium.com/@valyala/promql-tutorial-for-beginners-9ab455142085) and provides additional functionality mentioned below, which is aimed towards solving practical cases. Feel free [filing a feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues) if you think MetricsQL misses certain useful functionality.
 
-This functionality can be evaluated at [an editable Grafana dashboard](http://play-grafana.victoriametrics.com:3000/d/4ome8yJmz/node-exporter-on-victoriametrics-demo) or at your own [VcitoriaMetrics instance](https://docs.victoriametrics.com/#how-to-start-victoriametrics).
+This functionality can be evaluated at [an editable Grafana dashboard](https://play-grafana.victoriametrics.com/d/4ome8yJmz/node-exporter-on-victoriametrics-demo) or at your own [VictoriaMetrics instance](https://docs.victoriametrics.com/#how-to-start-victoriametrics).
 
 - Graphite-compatible filters can be passed via `{__graphite__="foo.*.bar"}` syntax. This is equivalent to `{__name__=~"foo[.][^.]*[.]bar"}`, but usually works faster and is easier to use when migrating from Graphite. VictoriaMetrics also can be used as Graphite datasource in Grafana. See [these docs](https://docs.victoriametrics.com/#graphite-api-usage) for details.
 - Lookbehind window in square brackets may be omitted. VictoriaMetrics automatically selects the lookbehind window depending on the current step used for building the graph (e.g. `step` query arg passed to [/api/v1/query_range](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries)). For instance, the following query is valid in VictoriaMetrics: `rate(node_network_receive_bytes_total)`. It is equivalent to `rate(node_network_receive_bytes_total[$__interval])` when used in Grafana.
diff --git a/lib/storage/tag_filters.go b/lib/storage/tag_filters.go
index 1b5b7c14e..5511d3fd7 100644
--- a/lib/storage/tag_filters.go
+++ b/lib/storage/tag_filters.go
@@ -18,58 +18,102 @@ import (
 //
 // This converts `foo{bar="baz",x=~"a.+"}` to `{foo=bar="baz",foo=x=~"a.+"} filter.
 func convertToCompositeTagFilterss(tfss []*TagFilters) []*TagFilters {
-	tfssNew := make([]*TagFilters, len(tfss))
-	for i, tfs := range tfss {
-		tfssNew[i] = convertToCompositeTagFilters(tfs)
+	tfssNew := make([]*TagFilters, 0, len(tfss))
+	for _, tfs := range tfss {
+		tfssNew = append(tfssNew, convertToCompositeTagFilters(tfs)...)
 	}
 	return tfssNew
 }
 
-func convertToCompositeTagFilters(tfs *TagFilters) *TagFilters {
-	// Search for metric name filter, which must be used for creating composite filters.
-	var name []byte
+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
 	hasPositiveFilter := false
 	for _, tf := range tfs.tfs {
-		if len(tf.key) == 0 && !tf.isNegative && !tf.isRegexp {
-			name = tf.value
+		if len(tf.key) == 0 {
+			if !tf.isNegative && !tf.isRegexp {
+				names = [][]byte{tf.value}
+			} else if !tf.isNegative && tf.isRegexp && len(tf.orSuffixes) > 0 {
+				// Split the filter {__name__=~"name1|...|nameN", other_filters}
+				// into name1{other_filters}, ..., nameN{other_filters}
+				// and generate composite filters for each of them.
+				names = names[:0] // override the previous filters on metric name
+				for _, orSuffix := range tf.orSuffixes {
+					names = append(names, []byte(orSuffix))
+				}
+			}
 		} else if !tf.isNegative && !tf.isEmptyMatch {
 			hasPositiveFilter = true
 		}
 	}
-	if len(name) == 0 {
+	if len(names) == 0 {
 		atomic.AddUint64(&compositeFilterMissingConversions, 1)
-		return tfs
+		return []*TagFilters{tfs}
 	}
-	tfsNew := make([]tagFilter, 0, len(tfs.tfs))
+
+	// Create composite filters for the found names.
 	var compositeKey []byte
-	compositeFilters := 0
-	for _, tf := range tfs.tfs {
-		if len(tf.key) == 0 {
-			if !hasPositiveFilter || tf.isNegative || tf.isRegexp || string(tf.value) != string(name) {
+	for _, name := range names {
+		compositeFilters := 0
+		tfsNew := make([]tagFilter, 0, len(tfs.tfs))
+		for _, tf := range tfs.tfs {
+			if len(tf.key) == 0 {
+				if !hasPositiveFilter || tf.isNegative {
+					// Negative filters on metric name cannot be used for building composite filter, so leave them as is.
+					tfsNew = append(tfsNew, tf)
+					continue
+				}
+				if tf.isRegexp {
+					matchName := false
+					for _, orSuffix := range tf.orSuffixes {
+						if orSuffix == string(name) {
+							matchName = true
+							break
+						}
+					}
+					if !matchName {
+						// Leave as is the regexp filter on metric name if it doesn't match the current name.
+						tfsNew = append(tfsNew, tf)
+						continue
+					}
+					// Skip the tf, since its part (name) is used as a prefix in composite filter.
+					continue
+				}
+				if string(tf.value) != string(name) {
+					// Leave as is the filter on another metric name.
+					tfsNew = append(tfsNew, tf)
+					continue
+				}
+				// Skip the tf, since it is used as a prefix in composite filter.
+				continue
+			}
+			if string(tf.key) == "__graphite__" || bytes.Equal(tf.key, graphiteReverseTagKey) {
+				// Leave as is __graphite__ filters, since they cannot be used for building composite filter.
 				tfsNew = append(tfsNew, tf)
+				continue
 			}
-			continue
-		}
-		if string(tf.key) == "__graphite__" || bytes.Equal(tf.key, graphiteReverseTagKey) {
-			tfsNew = append(tfsNew, tf)
-			continue
+			// Create composite filter on (name, tf)
+			compositeKey = marshalCompositeTagKey(compositeKey[:0], name, 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)
+			}
+			tfsNew = append(tfsNew, tfNew)
+			compositeFilters++
 		}
-		compositeKey = marshalCompositeTagKey(compositeKey[:0], name, 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)
+		if compositeFilters == 0 {
+			// Cannot use tfsNew, since it doesn't contain composite filters, e.g. it may match broader set of series.
+			// Fall back to the original tfs.
+			atomic.AddUint64(&compositeFilterMissingConversions, 1)
+			return []*TagFilters{tfs}
 		}
-		tfsNew = append(tfsNew, tfNew)
-		compositeFilters++
-	}
-	if compositeFilters == 0 {
-		atomic.AddUint64(&compositeFilterMissingConversions, 1)
-		return tfs
+		tfsCompiled := NewTagFilters()
+		tfsCompiled.tfs = tfsNew
+		tfssCompiled = append(tfssCompiled, tfsCompiled)
 	}
-	tfsCompiled := NewTagFilters()
-	tfsCompiled.tfs = tfsNew
 	atomic.AddUint64(&compositeFilterSuccessConversions, 1)
-	return tfsCompiled
+	return tfssCompiled
 }
 
 var (


Test patch:
diff --git a/lib/storage/tag_filters_test.go b/lib/storage/tag_filters_test.go
index fe4716c8a..9fe164f86 100644
--- a/lib/storage/tag_filters_test.go
+++ b/lib/storage/tag_filters_test.go
@@ -7,7 +7,7 @@ import (
 )
 
 func TestConvertToCompositeTagFilters(t *testing.T) {
-	f := func(tfs, resultExpected []TagFilter) {
+	f := func(tfs []TagFilter, resultExpected [][]TagFilter) {
 		t.Helper()
 		tfsCompiled := NewTagFilters()
 		for _, tf := range tfs {
@@ -15,15 +15,19 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
 				t.Fatalf("cannot add tf=%s: %s", tf.String(), err)
 			}
 		}
-		resultCompiled := convertToCompositeTagFilters(tfsCompiled)
-		result := make([]TagFilter, len(resultCompiled.tfs))
-		for i, tf := range resultCompiled.tfs {
-			result[i] = TagFilter{
-				Key:        tf.key,
-				Value:      tf.value,
-				IsNegative: tf.isNegative,
-				IsRegexp:   tf.isRegexp,
+		resultsCompiled := convertToCompositeTagFilterss([]*TagFilters{tfsCompiled})
+		result := make([][]TagFilter, len(resultsCompiled))
+		for i, resultCompiled := range resultsCompiled {
+			tfs := make([]TagFilter, len(resultCompiled.tfs))
+			for i, tf := range resultCompiled.tfs {
+				tfs[i] = TagFilter{
+					Key:        tf.key,
+					Value:      tf.value,
+					IsNegative: tf.isNegative,
+					IsRegexp:   tf.isRegexp,
+				}
 			}
+			result[i] = tfs
 		}
 		if !reflect.DeepEqual(result, resultExpected) {
 			t.Fatalf("unexpected result;\ngot\n%+v\nwant\n%+v", result, resultExpected)
@@ -31,7 +35,7 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
 	}
 
 	// Empty filters
-	f(nil, []TagFilter{})
+	f(nil, [][]TagFilter{{}})
 
 	// A single non-name filter
 	f([]TagFilter{
@@ -41,12 +45,14 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
 			IsNegative: false,
 			IsRegexp:   false,
 		},
-	}, []TagFilter{
+	}, [][]TagFilter{
 		{
-			Key:        []byte("foo"),
-			Value:      []byte("bar"),
-			IsNegative: false,
-			IsRegexp:   false,
+			{
+				Key:        []byte("foo"),
+				Value:      []byte("bar"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
 		},
 	})
 
@@ -64,18 +70,20 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
 			IsNegative: true,
 			IsRegexp:   false,
 		},
-	}, []TagFilter{
+	}, [][]TagFilter{
 		{
-			Key:        []byte("foo"),
-			Value:      []byte("bar"),
-			IsNegative: false,
-			IsRegexp:   false,
-		},
-		{
-			Key:        []byte("x"),
-			Value:      []byte("yy"),
-			IsNegative: true,
-			IsRegexp:   false,
+			{
+				Key:        []byte("foo"),
+				Value:      []byte("bar"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
+			{
+				Key:        []byte("x"),
+				Value:      []byte("yy"),
+				IsNegative: true,
+				IsRegexp:   false,
+			},
 		},
 	})
 
@@ -87,12 +95,14 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
 			IsNegative: false,
 			IsRegexp:   false,
 		},
-	}, []TagFilter{
+	}, [][]TagFilter{
 		{
-			Key:        nil,
-			Value:      []byte("bar"),
-			IsNegative: false,
-			IsRegexp:   false,
+			{
+				Key:        nil,
+				Value:      []byte("bar"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
 		},
 	})
 
@@ -110,18 +120,20 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
 			IsNegative: false,
 			IsRegexp:   false,
 		},
-	}, []TagFilter{
-		{
-			Key:        nil,
-			Value:      []byte("bar"),
-			IsNegative: false,
-			IsRegexp:   false,
-		},
+	}, [][]TagFilter{
 		{
-			Key:        nil,
-			Value:      []byte("baz"),
-			IsNegative: false,
-			IsRegexp:   false,
+			{
+				Key:        nil,
+				Value:      []byte("bar"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
+			{
+				Key:        nil,
+				Value:      []byte("baz"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
 		},
 	})
 
@@ -139,12 +151,14 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
 			IsNegative: false,
 			IsRegexp:   false,
 		},
-	}, []TagFilter{
+	}, [][]TagFilter{
 		{
-			Key:        []byte("\xfe\x03barfoo"),
-			Value:      []byte("abc"),
-			IsNegative: false,
-			IsRegexp:   false,
+			{
+				Key:        []byte("\xfe\x03barfoo"),
+				Value:      []byte("abc"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
 		},
 	})
 
@@ -162,18 +176,20 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
 			IsNegative: true,
 			IsRegexp:   false,
 		},
-	}, []TagFilter{
+	}, [][]TagFilter{
 		{
-			Key:        nil,
-			Value:      []byte("bar"),
-			IsNegative: false,
-			IsRegexp:   false,
-		},
-		{
-			Key:        []byte("\xfe\x03barfoo"),
-			Value:      []byte("abc"),
-			IsNegative: true,
-			IsRegexp:   false,
+			{
+				Key:        nil,
+				Value:      []byte("bar"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
+			{
+				Key:        []byte("\xfe\x03barfoo"),
+				Value:      []byte("abc"),
+				IsNegative: true,
+				IsRegexp:   false,
+			},
 		},
 	})
 
@@ -197,18 +213,20 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
 			IsNegative: false,
 			IsRegexp:   true,
 		},
-	}, []TagFilter{
+	}, [][]TagFilter{
 		{
-			Key:        []byte("\xfe\x03barfoo"),
-			Value:      []byte("abc"),
-			IsNegative: true,
-			IsRegexp:   false,
-		},
-		{
-			Key:        []byte("\xfe\x03bara"),
-			Value:      []byte("b.+"),
-			IsNegative: false,
-			IsRegexp:   true,
+			{
+				Key:        []byte("\xfe\x03barfoo"),
+				Value:      []byte("abc"),
+				IsNegative: true,
+				IsRegexp:   false,
+			},
+			{
+				Key:        []byte("\xfe\x03bara"),
+				Value:      []byte("b.+"),
+				IsNegative: false,
+				IsRegexp:   true,
+			},
 		},
 	})
 
@@ -232,18 +250,20 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
 			IsNegative: false,
 			IsRegexp:   false,
 		},
-	}, []TagFilter{
+	}, [][]TagFilter{
 		{
-			Key:        nil,
-			Value:      []byte("bar"),
-			IsNegative: false,
-			IsRegexp:   false,
-		},
-		{
-			Key:        []byte("\xfe\x03bazfoo"),
-			Value:      []byte("abc"),
-			IsNegative: false,
-			IsRegexp:   false,
+			{
+				Key:        nil,
+				Value:      []byte("bar"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
+			{
+				Key:        []byte("\xfe\x03bazfoo"),
+				Value:      []byte("abc"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
 		},
 	})
 
@@ -261,12 +281,14 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
 			IsNegative: false,
 			IsRegexp:   true,
 		},
-	}, []TagFilter{
+	}, [][]TagFilter{
 		{
-			Key:        []byte("\xfe\x03barfoo"),
-			Value:      []byte("abc"),
-			IsNegative: false,
-			IsRegexp:   false,
+			{
+				Key:        []byte("\xfe\x03barfoo"),
+				Value:      []byte("abc"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
 		},
 	})
 
@@ -284,12 +306,14 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
 			IsNegative: false,
 			IsRegexp:   true,
 		},
-	}, []TagFilter{
+	}, [][]TagFilter{
 		{
-			Key:        []byte("\xfe\x03barfoo"),
-			Value:      []byte("abc.+"),
-			IsNegative: false,
-			IsRegexp:   true,
+			{
+				Key:        []byte("\xfe\x03barfoo"),
+				Value:      []byte("abc.+"),
+				IsNegative: false,
+				IsRegexp:   true,
+			},
 		},
 	})
 
@@ -307,84 +331,174 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
 			IsNegative: false,
 			IsRegexp:   false,
 		},
-	}, []TagFilter{
+	}, [][]TagFilter{
+		{
+			{
+				Key:        nil,
+				Value:      []byte("bar"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
+			{
+				Key:        []byte("__graphite__"),
+				Value:      []byte("foo.*.bar"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
+		},
+	})
+
+	// A name filter with non-name filter and a graphite filter.
+	f([]TagFilter{
 		{
 			Key:        nil,
 			Value:      []byte("bar"),
 			IsNegative: false,
 			IsRegexp:   false,
 		},
+		{
+			Key:        []byte("foo"),
+			Value:      []byte("abc"),
+			IsNegative: false,
+			IsRegexp:   true,
+		},
 		{
 			Key:        []byte("__graphite__"),
 			Value:      []byte("foo.*.bar"),
 			IsNegative: false,
 			IsRegexp:   false,
 		},
+	}, [][]TagFilter{
+		{
+			{
+				Key:        []byte("\xfe\x03barfoo"),
+				Value:      []byte("abc"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
+			{
+				Key:        []byte("__graphite__"),
+				Value:      []byte("foo.*.bar"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
+		},
 	})
 
-	// A name filter with non-name filter and a graphite filter.
+	// Regexp name filter, which can be converted to non-regexp, with non-name filter.
 	f([]TagFilter{
 		{
 			Key:        nil,
 			Value:      []byte("bar"),
 			IsNegative: false,
-			IsRegexp:   false,
+			IsRegexp:   true,
 		},
 		{
 			Key:        []byte("foo"),
 			Value:      []byte("abc"),
 			IsNegative: false,
-			IsRegexp:   true,
+			IsRegexp:   false,
 		},
+	}, [][]TagFilter{
 		{
-			Key:        []byte("__graphite__"),
-			Value:      []byte("foo.*.bar"),
+			{
+				Key:        []byte("\xfe\x03barfoo"),
+				Value:      []byte("abc"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
+		},
+	})
+
+	// Multiple values regexp filter, which can be converted to non-regexp, with non-name filter.
+	f([]TagFilter{
+		{
+			Key:        nil,
+			Value:      []byte("bar|foo"),
 			IsNegative: false,
-			IsRegexp:   false,
+			IsRegexp:   true,
 		},
-	}, []TagFilter{
 		{
-			Key:        []byte("\xfe\x03barfoo"),
+			Key:        []byte("foo"),
 			Value:      []byte("abc"),
 			IsNegative: false,
 			IsRegexp:   false,
 		},
+	}, [][]TagFilter{
 		{
-			Key:        []byte("__graphite__"),
-			Value:      []byte("foo.*.bar"),
-			IsNegative: false,
-			IsRegexp:   false,
+			{
+				Key:        []byte("\xfe\x03barfoo"),
+				Value:      []byte("abc"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
+		},
+		{
+			{
+				Key:        []byte("\xfe\x03foofoo"),
+				Value:      []byte("abc"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
 		},
 	})
 
-	// Regexp name filter, which can be converted to non-regexp, with non-name filter.
+	// Two multiple values regexp filter, which can be converted to non-regexp, with non-name filter.
 	f([]TagFilter{
 		{
 			Key:        nil,
-			Value:      []byte("bar"),
+			Value:      []byte("bar|foo"),
 			IsNegative: false,
 			IsRegexp:   true,
 		},
 		{
-			Key:        []byte("foo"),
-			Value:      []byte("abc"),
+			Key:        nil,
+			Value:      []byte("abc|def"),
 			IsNegative: false,
-			IsRegexp:   false,
+			IsRegexp:   true,
 		},
-	}, []TagFilter{
 		{
-			Key:        []byte("\xfe\x03barfoo"),
-			Value:      []byte("abc"),
+			Key:        []byte("face"),
+			Value:      []byte("air"),
 			IsNegative: false,
 			IsRegexp:   false,
 		},
+	}, [][]TagFilter{
+		{
+			{
+				Key:        nil,
+				Value:      []byte("bar|foo"),
+				IsNegative: false,
+				IsRegexp:   true,
+			},
+			{
+				Key:        []byte("\xfe\x03abcface"),
+				Value:      []byte("air"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
+		},
+		{
+			{
+				Key:        nil,
+				Value:      []byte("bar|foo"),
+				IsNegative: false,
+				IsRegexp:   true,
+			},
+			{
+				Key:        []byte("\xfe\x03defface"),
+				Value:      []byte("air"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
+		},
 	})
 
-	// Regexp name filter with non-name filter.
+	// Multiple values regexp filter with a single negative filter
 	f([]TagFilter{
 		{
 			Key:        nil,
-			Value:      []byte("bar.+"),
+			Value:      []byte("bar|foo"),
 			IsNegative: false,
 			IsRegexp:   true,
 		},
@@ -394,7 +508,39 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
 			IsNegative: true,
 			IsRegexp:   false,
 		},
-	}, []TagFilter{
+	}, [][]TagFilter{
+		{
+			{
+				Key:        nil,
+				Value:      []byte("bar|foo"),
+				IsNegative: false,
+				IsRegexp:   true,
+			},
+			{
+				Key:        []byte("\xfe\x03barfoo"),
+				Value:      []byte("abc"),
+				IsNegative: true,
+				IsRegexp:   false,
+			},
+		},
+		{
+			{
+				Key:        nil,
+				Value:      []byte("bar|foo"),
+				IsNegative: false,
+				IsRegexp:   true,
+			},
+			{
+				Key:        []byte("\xfe\x03foofoo"),
+				Value:      []byte("abc"),
+				IsNegative: true,
+				IsRegexp:   false,
+			},
+		},
+	})
+
+	// Regexp name filter with non-name filter.
+	f([]TagFilter{
 		{
 			Key:        nil,
 			Value:      []byte("bar.+"),
@@ -407,6 +553,21 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
 			IsNegative: true,
 			IsRegexp:   false,
 		},
+	}, [][]TagFilter{
+		{
+			{
+				Key:        nil,
+				Value:      []byte("bar.+"),
+				IsNegative: false,
+				IsRegexp:   true,
+			},
+			{
+				Key:        []byte("foo"),
+				Value:      []byte("abc"),
+				IsNegative: true,
+				IsRegexp:   false,
+			},
+		},
 	})
 
 	// Regexp non-name filter, which matches anything.
@@ -423,12 +584,14 @@ func TestConvertToCompositeTagFilters(t *testing.T) {
 			IsNegative: false,
 			IsRegexp:   true,
 		},
-	}, []TagFilter{
+	}, [][]TagFilter{
 		{
-			Key:        nil,
-			Value:      []byte("bar"),
-			IsNegative: false,
-			IsRegexp:   false,
+			{
+				Key:        nil,
+				Value:      []byte("bar"),
+				IsNegative: false,
+				IsRegexp:   false,
+			},
 		},
 	})
 }