Repository: VictoriaMetrics/VictoriaMetrics
Pull request #1079 (2021-02-20 04:15:07)

lib/storage: correct tagfilter match cost


Patch:
diff --git a/app/vmagent/remotewrite/relabel.go b/app/vmagent/remotewrite/relabel.go
index 875694173..ad7c967f6 100644
--- a/app/vmagent/remotewrite/relabel.go
+++ b/app/vmagent/remotewrite/relabel.go
@@ -41,7 +41,7 @@ func loadRelabelConfigs() (*relabelConfigs, error) {
 		return nil, fmt.Errorf("too many -remoteWrite.urlRelabelConfig args: %d; it mustn't exceed the number of -remoteWrite.url args: %d",
 			len(*relabelConfigPaths), len(*remoteWriteURLs))
 	}
-	rcs.perURL = make([][]promrelabel.ParsedRelabelConfig, len(*remoteWriteURLs))
+	rcs.perURL = make([]*promrelabel.ParsedConfigs, len(*remoteWriteURLs))
 	for i, path := range *relabelConfigPaths {
 		if len(path) == 0 {
 			// Skip empty relabel config.
@@ -57,8 +57,8 @@ func loadRelabelConfigs() (*relabelConfigs, error) {
 }
 
 type relabelConfigs struct {
-	global []promrelabel.ParsedRelabelConfig
-	perURL [][]promrelabel.ParsedRelabelConfig
+	global *promrelabel.ParsedConfigs
+	perURL []*promrelabel.ParsedConfigs
 }
 
 // initLabelsGlobal must be called after parsing command-line flags.
@@ -79,8 +79,8 @@ func initLabelsGlobal() {
 	}
 }
 
-func (rctx *relabelCtx) applyRelabeling(tss []prompbmarshal.TimeSeries, extraLabels []prompbmarshal.Label, prcs []promrelabel.ParsedRelabelConfig) []prompbmarshal.TimeSeries {
-	if len(extraLabels) == 0 && len(prcs) == 0 {
+func (rctx *relabelCtx) applyRelabeling(tss []prompbmarshal.TimeSeries, extraLabels []prompbmarshal.Label, pcs *promrelabel.ParsedConfigs) []prompbmarshal.TimeSeries {
+	if len(extraLabels) == 0 && pcs.Len() == 0 {
 		// Nothing to change.
 		return tss
 	}
@@ -100,7 +100,7 @@ func (rctx *relabelCtx) applyRelabeling(tss []prompbmarshal.TimeSeries, extraLab
 				labels = append(labels, *extraLabel)
 			}
 		}
-		labels = promrelabel.ApplyRelabelConfigs(labels, labelsLen, prcs, true)
+		labels = pcs.Apply(labels, labelsLen, true)
 		if len(labels) == labelsLen {
 			// Drop the current time series, since relabeling removed all the labels.
 			continue
diff --git a/app/vmagent/remotewrite/remotewrite.go b/app/vmagent/remotewrite/remotewrite.go
index 824ea9ad7..4e5ef6751 100644
--- a/app/vmagent/remotewrite/remotewrite.go
+++ b/app/vmagent/remotewrite/remotewrite.go
@@ -142,8 +142,8 @@ func Stop() {
 func Push(wr *prompbmarshal.WriteRequest) {
 	var rctx *relabelCtx
 	rcs := allRelabelConfigs.Load().(*relabelConfigs)
-	prcsGlobal := rcs.global
-	if len(prcsGlobal) > 0 || len(labelsGlobal) > 0 {
+	pcsGlobal := rcs.global
+	if pcsGlobal.Len() > 0 || len(labelsGlobal) > 0 {
 		rctx = getRelabelCtx()
 	}
 	tss := wr.Timeseries
@@ -167,7 +167,7 @@ func Push(wr *prompbmarshal.WriteRequest) {
 		}
 		if rctx != nil {
 			tssBlockLen := len(tssBlock)
-			tssBlock = rctx.applyRelabeling(tssBlock, labelsGlobal, prcsGlobal)
+			tssBlock = rctx.applyRelabeling(tssBlock, labelsGlobal, pcsGlobal)
 			globalRelabelMetricsDropped.Add(tssBlockLen - len(tssBlock))
 		}
 		for _, rwctx := range rwctxs {
@@ -240,8 +240,8 @@ func (rwctx *remoteWriteCtx) Push(tss []prompbmarshal.TimeSeries) {
 	var rctx *relabelCtx
 	var v *[]prompbmarshal.TimeSeries
 	rcs := allRelabelConfigs.Load().(*relabelConfigs)
-	prcs := rcs.perURL[rwctx.idx]
-	if len(prcs) > 0 {
+	pcs := rcs.perURL[rwctx.idx]
+	if pcs.Len() > 0 {
 		rctx = getRelabelCtx()
 		// Make a copy of tss before applying relabeling in order to prevent
 		// from affecting time series for other remoteWrite.url configs.
@@ -250,7 +250,7 @@ func (rwctx *remoteWriteCtx) Push(tss []prompbmarshal.TimeSeries) {
 		v = tssRelabelPool.Get().(*[]prompbmarshal.TimeSeries)
 		tss = append(*v, tss...)
 		tssLen := len(tss)
-		tss = rctx.applyRelabeling(tss, nil, prcs)
+		tss = rctx.applyRelabeling(tss, nil, pcs)
 		rwctx.relabelMetricsDropped.Add(tssLen - len(tss))
 	}
 	pss := rwctx.pss
diff --git a/app/vminsert/relabel/relabel.go b/app/vminsert/relabel/relabel.go
index 11b9cc774..b9a2e62e4 100644
--- a/app/vminsert/relabel/relabel.go
+++ b/app/vminsert/relabel/relabel.go
@@ -19,11 +19,11 @@ var relabelConfig = flag.String("relabelConfig", "", "Optional path to a file wi
 
 // Init must be called after flag.Parse and before using the relabel package.
 func Init() {
-	prcs, err := loadRelabelConfig()
+	pcs, err := loadRelabelConfig()
 	if err != nil {
 		logger.Fatalf("cannot load relabelConfig: %s", err)
 	}
-	prcsGlobal.Store(&prcs)
+	pcsGlobal.Store(pcs)
 	if len(*relabelConfig) == 0 {
 		return
 	}
@@ -31,34 +31,34 @@ func Init() {
 	go func() {
 		for range sighupCh {
 			logger.Infof("received SIGHUP; reloading -relabelConfig=%q...", *relabelConfig)
-			prcs, err := loadRelabelConfig()
+			pcs, err := loadRelabelConfig()
 			if err != nil {
 				logger.Errorf("cannot load the updated relabelConfig: %s; preserving the previous config", err)
 				continue
 			}
-			prcsGlobal.Store(&prcs)
+			pcsGlobal.Store(pcs)
 			logger.Infof("successfully reloaded -relabelConfig=%q", *relabelConfig)
 		}
 	}()
 }
 
-var prcsGlobal atomic.Value
+var pcsGlobal atomic.Value
 
-func loadRelabelConfig() ([]promrelabel.ParsedRelabelConfig, error) {
+func loadRelabelConfig() (*promrelabel.ParsedConfigs, error) {
 	if len(*relabelConfig) == 0 {
 		return nil, nil
 	}
-	prcs, err := promrelabel.LoadRelabelConfigs(*relabelConfig)
+	pcs, err := promrelabel.LoadRelabelConfigs(*relabelConfig)
 	if err != nil {
 		return nil, fmt.Errorf("error when reading -relabelConfig=%q: %w", *relabelConfig, err)
 	}
-	return prcs, nil
+	return pcs, nil
 }
 
 // HasRelabeling returns true if there is global relabeling.
 func HasRelabeling() bool {
-	prcs := prcsGlobal.Load().(*[]promrelabel.ParsedRelabelConfig)
-	return len(*prcs) > 0
+	pcs := pcsGlobal.Load().(*promrelabel.ParsedConfigs)
+	return pcs.Len() > 0
 }
 
 // Ctx holds relabeling context.
@@ -77,8 +77,8 @@ func (ctx *Ctx) Reset() {
 //
 // The returned labels are valid until the next call to ApplyRelabeling.
 func (ctx *Ctx) ApplyRelabeling(labels []prompb.Label) []prompb.Label {
-	prcs := prcsGlobal.Load().(*[]promrelabel.ParsedRelabelConfig)
-	if len(*prcs) == 0 {
+	pcs := pcsGlobal.Load().(*promrelabel.ParsedConfigs)
+	if pcs.Len() == 0 {
 		// There are no relabeling rules.
 		return labels
 	}
@@ -97,7 +97,7 @@ func (ctx *Ctx) ApplyRelabeling(labels []prompb.Label) []prompb.Label {
 	}
 
 	// Apply relabeling
-	tmpLabels = promrelabel.ApplyRelabelConfigs(tmpLabels, 0, *prcs, true)
+	tmpLabels = pcs.Apply(tmpLabels, 0, true)
 	ctx.tmpLabels = tmpLabels
 	if len(tmpLabels) == 0 {
 		metricsDropped.Inc()
diff --git a/app/vmselect/promql/rollup.go b/app/vmselect/promql/rollup.go
index 3c62fbdc7..c7f973a82 100644
--- a/app/vmselect/promql/rollup.go
+++ b/app/vmselect/promql/rollup.go
@@ -53,6 +53,7 @@ var rollupFuncs = map[string]newRollupFunc{
 	"distinct_over_time":    newRollupFuncOneArg(rollupDistinct),
 	"increases_over_time":   newRollupFuncOneArg(rollupIncreases),
 	"decreases_over_time":   newRollupFuncOneArg(rollupDecreases),
+	"increase_pure":         newRollupFuncOneArg(rollupIncreasePure), // + rollupFuncsRemoveCounterResets
 	"integrate":             newRollupFuncOneArg(rollupIntegrate),
 	"ideriv":                newRollupFuncOneArg(rollupIderiv),
 	"lifetime":              newRollupFuncOneArg(rollupLifetime),
@@ -123,6 +124,7 @@ var rollupAggrFuncs = map[string]rollupFunc{
 	"distinct_over_time":  rollupDistinct,
 	"increases_over_time": rollupIncreases,
 	"decreases_over_time": rollupDecreases,
+	"increase_pure":       rollupIncreasePure,
 	"integrate":           rollupIntegrate,
 	"ideriv":              rollupIderiv,
 	"lifetime":            rollupLifetime,
@@ -160,6 +162,7 @@ var rollupFuncsCannotAdjustWindow = map[string]bool{
 	"distinct_over_time":  true,
 	"increases_over_time": true,
 	"decreases_over_time": true,
+	"increase_pure":       true,
 	"integrate":           true,
 	"ascent_over_time":    true,
 	"descent_over_time":   true,
@@ -172,6 +175,7 @@ var rollupFuncsRemoveCounterResets = map[string]bool{
 	"rate":            true,
 	"rollup_rate":     true,
 	"rollup_increase": true,
+	"increase_pure":   true,
 }
 
 var rollupFuncsKeepMetricGroup = map[string]bool{
@@ -1323,6 +1327,25 @@ func rollupStdvar(rfa *rollupFuncArg) float64 {
 	return q / count
 }
 
+func rollupIncreasePure(rfa *rollupFuncArg) float64 {
+	// There is no need in handling NaNs here, since they must be cleaned up
+	// before calling rollup funcs.
+	values := rfa.values
+	prevValue := rfa.prevValue
+	if math.IsNaN(prevValue) {
+		if len(values) == 0 {
+			return nan
+		}
+		// Assume the counter starts from 0.
+		prevValue = 0
+	}
+	if len(values) == 0 {
+		// Assume the counter didsn't change since prevValue.
+		return 0
+	}
+	return values[len(values)-1] - prevValue
+}
+
 func rollupDelta(rfa *rollupFuncArg) float64 {
 	// There is no need in handling NaNs here, since they must be cleaned up
 	// before calling rollup funcs.
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 94403e514..a8ddf8c3e 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -3,6 +3,17 @@
 # tip
 
 * FEATURE: vmagent: add `scrape_align_interval` config option, which can be used for aligning scrapes to the beginning of the configured interval. See [these docs](https://victoriametrics.github.io/vmagent.html#troubleshooting) for details.
+* FEATURE: expose io-related metrics at `/metrics` page for every VictoriaMetrics component:
+  * `process_io_read_bytes_total` - the number of bytes read via io syscalls such as read and pread
+  * `process_io_written_bytes_total` - the number of bytes written via io syscalls such as write and pwrite
+  * `process_io_read_syscalls_total` - the number of read syscalls such as read and pread
+  * `process_io_write_syscalls_total` - the number of write syscalls such as write and pwrite
+  * `process_io_storage_read_bytes_total` - the number of bytes read from storage layer
+  * `process_io_storage_written_bytes_total` - the number of bytes written to storage layer
+* FEATURE: vmagent: export `vm_promscrape_target_relabel_duration_seconds` metric, which can be used for monitoring the time spend on relabeling for discovered targets.
+* FEATURE: vmagent: optimize [relabeling](https://victoriametrics.github.io/vmagent.html#relabeling) performance for common cases.
+* FEATURE: add `increase_pure(m[d])` function to MetricsQL. It works the same as `increase(m[d])` except of various edge cases. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/962) for details.
+
 
 * BUGFIX: vmagent: properly perform graceful shutdown on `SIGINT` and `SIGTERM` signals. The graceful shutdown has been broken in `v1.54.0`. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1065
 * BUGFIX: reduce the probability of `duplicate time series` errors when querying Kubernetes metrics.
diff --git a/docs/MetricsQL.md b/docs/MetricsQL.md
index 65d75612c..15bf2638c 100644
--- a/docs/MetricsQL.md
+++ b/docs/MetricsQL.md
@@ -72,6 +72,7 @@ This functionality can be tried at [an editable Grafana dashboard](http://play-g
 - `start()` and `end()` functions for returning the start and end timestamps of the `[start ... end]` range used in the query.
 - `integrate(m[d])` for returning integral over the given duration `d` for the given metric `m`.
 - `ideriv(m[d])` - for calculating `instant` derivative for the metric `m` over the duration `d`.
+- `increase_pure(m[d])` - for calculating increase of `m` over `d` without edge-case handling compared to `increase(m[d])`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/962) for details.
 - `deriv_fast(m[d])` - for calculating `fast` derivative for `m` based on the first and the last points from duration `d`.
 - `running_` functions - `running_sum`, `running_min`, `running_max`, `running_avg` - for calculating [running values](https://en.wikipedia.org/wiki/Running_total) on the selected time range.
 - `range_` functions - `range_sum`, `range_min`, `range_max`, `range_avg`, `range_first`, `range_last`, `range_median`, `range_quantile` - for calculating global value over the selected time range. Note that global value is based on calculated datapoints for the inner query. The calculated datapoints can differ from raw datapoints stored in the database. See [these docs](https://prometheus.io/docs/prometheus/latest/querying/basics/#staleness) for details.
diff --git a/docs/Single-server-VictoriaMetrics.md b/docs/Single-server-VictoriaMetrics.md
index 54526ccdb..7aba95a44 100644
--- a/docs/Single-server-VictoriaMetrics.md
+++ b/docs/Single-server-VictoriaMetrics.md
@@ -605,7 +605,8 @@ and it is easier to use when migrating from Graphite to VictoriaMetrics.
 ### Graphite Render API usage
 
 [VictoriaMetrics Enterprise](https://victoriametrics.com/enterprise.html) supports [Graphite Render API](https://graphite.readthedocs.io/en/stable/render_api.html) subset
-at `/render` endpoint. This subset is required for [Graphite datasource in Grafana](https://grafana.com/docs/grafana/latest/datasources/graphite/).
+at `/render` endpoint, which is used by [Graphite datasource in Grafana](https://grafana.com/docs/grafana/latest/datasources/graphite/).
+It supports `Storage-Step` http request header, which must be set to a step between data points stored in VictoriaMetrics when configuring Graphite datasource in Grafana.
 
 
 ### Graphite Metrics API usage
diff --git a/go.mod b/go.mod
index 857a1b9f7..fe007c0dd 100644
--- a/go.mod
+++ b/go.mod
@@ -8,8 +8,8 @@ require (
 	// Do not use the original github.com/valyala/fasthttp because of issues
 	// like https://github.com/valyala/fasthttp/commit/996610f021ff45fdc98c2ce7884d5fa4e7f9199b
 	github.com/VictoriaMetrics/fasthttp v1.0.12
-	github.com/VictoriaMetrics/metrics v1.14.0
-	github.com/VictoriaMetrics/metricsql v0.10.1
+	github.com/VictoriaMetrics/metrics v1.15.0
+	github.com/VictoriaMetrics/metricsql v0.11.0
 	github.com/aws/aws-sdk-go v1.37.12
 	github.com/cespare/xxhash/v2 v2.1.1
 	github.com/cheggaaa/pb/v3 v3.0.6
diff --git a/go.sum b/go.sum
index 5f0da9295..343c504a0 100644
--- a/go.sum
+++ b/go.sum
@@ -85,10 +85,10 @@ github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6Ro
 github.com/VictoriaMetrics/fasthttp v1.0.12 h1:Ag0E119yrH4BTxVyjKD9TeiSImtG9bUcg/stItLJhSE=
 github.com/VictoriaMetrics/fasthttp v1.0.12/go.mod h1:3SeUL4zwB/p/a9aEeRc6gdlbrtNHXBJR6N376EgiSHU=
 github.com/VictoriaMetrics/metrics v1.12.2/go.mod h1:Z1tSfPfngDn12bTfZSCqArT3OPY3u88J12hSoOhuiRE=
-github.com/VictoriaMetrics/metrics v1.14.0 h1:yvyEVo7cPN2Hv+Hrm1zPTA1f/squmEZTq6xtPH/8F64=
-github.com/VictoriaMetrics/metrics v1.14.0/go.mod h1:Z1tSfPfngDn12bTfZSCqArT3OPY3u88J12hSoOhuiRE=
-github.com/VictoriaMetrics/metricsql v0.10.1 h1:wLl/YbMmBGFPyLKMfqNLC333iygibosSM5iSvlH2B4A=
-github.com/VictoriaMetrics/metricsql v0.10.1/go.mod h1:ylO7YITho/Iw6P71oEaGyHbO94bGoGtzWfLGqFhMIg8=
+github.com/VictoriaMetrics/metrics v1.15.0 h1:HGmGaILioC4vNk6UhkcwLIaDlg5y4MVganq1verl5js=
+github.com/VictoriaMetrics/metrics v1.15.0/go.mod h1:Z1tSfPfngDn12bTfZSCqArT3OPY3u88J12hSoOhuiRE=
+github.com/VictoriaMetrics/metricsql v0.11.0 h1:85zbY8NxWNALppctOAkWfLDC7dDFcvTEn5IHMKwOGag=
+github.com/VictoriaMetrics/metricsql v0.11.0/go.mod h1:ylO7YITho/Iw6P71oEaGyHbO94bGoGtzWfLGqFhMIg8=
 github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
 github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
 github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
diff --git a/lib/mergeset/block_header.go b/lib/mergeset/block_header.go
index 5404c2af6..49e35aa09 100644
--- a/lib/mergeset/block_header.go
+++ b/lib/mergeset/block_header.go
@@ -3,6 +3,7 @@ package mergeset
 import (
 	"fmt"
 	"sort"
+	"unsafe"
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
@@ -34,6 +35,10 @@ type blockHeader struct {
 	lensBlockSize uint32
 }
 
+func (bh *blockHeader) SizeBytes() int {
+	return int(unsafe.Sizeof(*bh)) + cap(bh.commonPrefix) + cap(bh.firstItem)
+}
+
 func (bh *blockHeader) Reset() {
 	bh.commonPrefix = bh.commonPrefix[:0]
 	bh.firstItem = bh.firstItem[:0]
diff --git a/lib/mergeset/block_stream_reader.go b/lib/mergeset/block_stream_reader.go
index a89710887..06a28b238 100644
--- a/lib/mergeset/block_stream_reader.go
+++ b/lib/mergeset/block_stream_reader.go
@@ -195,7 +195,8 @@ func (bsr *blockStreamReader) Next() bool {
 		if err := bsr.readNextBHS(); err != nil {
 			if err == io.EOF {
 				// Check the last item.
-				lastItem := bsr.Block.items[len(bsr.Block.items)-1]
+				b := &bsr.Block
+				lastItem := b.items[len(b.items)-1].Bytes(b.data)
 				if string(bsr.ph.lastItem) != string(lastItem) {
 					err = fmt.Errorf("unexpected last item; got %X; want %X", lastItem, bsr.ph.lastItem)
 				}
@@ -240,12 +241,13 @@ func (bsr *blockStreamReader) Next() bool {
 	}
 	if !bsr.firstItemChecked {
 		bsr.firstItemChecked = true
-		if string(bsr.ph.firstItem) != string(bsr.Block.items[0]) {
-			bsr.err = fmt.Errorf("unexpected first item; got %X; want %X", bsr.Block.items[0], bsr.ph.firstItem)
+		b := &bsr.Block
+		firstItem := b.items[0].Bytes(b.data)
+		if string(bsr.ph.firstItem) != string(firstItem) {
+			bsr.err = fmt.Errorf("unexpected first item; got %X; want %X", firstItem, bsr.ph.firstItem)
 			return false
 		}
 	}
-
 	return true
 }
 
diff --git a/lib/mergeset/encoding.go b/lib/mergeset/encoding.go
index 647b0a674..8cb57082c 100644
--- a/lib/mergeset/encoding.go
+++ b/lib/mergeset/encoding.go
@@ -3,6 +3,7 @@ package mergeset
 import (
 	"fmt"
 	"os"
+	"reflect"
 	"sort"
 	"strings"
 	"sync"
@@ -13,35 +14,62 @@ import (
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
 )
 
-type byteSliceSorter [][]byte
+// Item represents a single item for storing in a mergeset.
+type Item struct {
+	// Start is start offset for the item in data.
+	Start uint32
 
-func (s byteSliceSorter) Len() int { return len(s) }
-func (s byteSliceSorter) Less(i, j int) bool {
-	return string(s[i]) < string(s[j])
+	// End is end offset for the item in data.
+	End uint32
 }
-func (s byteSliceSorter) Swap(i, j int) {
-	s[i], s[j] = s[j], s[i]
+
+// Bytes returns bytes representation of it obtained from data.
+//
+// The returned bytes representation belongs to data.
+func (it Item) Bytes(data []byte) []byte {
+	sh := (*reflect.SliceHeader)(unsafe.Pointer(&data))
+	sh.Cap = int(it.End - it.Start)
+	sh.Len = int(it.End - it.Start)
+	sh.Data += uintptr(it.Start)
+	return data
+}
+
+// String returns string represetnation of it obtained from data.
+//
+// The returned string representation belongs to data.
+func (it Item) String(data []byte) string {
+	sh := (*reflect.SliceHeader)(unsafe.Pointer(&data))
+	sh.Data += uintptr(it.Start)
+	sh.Len = int(it.End - it.Start)
+	return *(*string)(unsafe.Pointer(sh))
+}
+
+func (ib *inmemoryBlock) Len() int { return len(ib.items) }
+
+func (ib *inmemoryBlock) Less(i, j int) bool {
+	data := ib.data
+	items := ib.items
+	return string(items[i].Bytes(data)) < string(items[j].Bytes(data))
+}
+
+func (ib *inmemoryBlock) Swap(i, j int) {
+	items := ib.items
+	items[i], items[j] = items[j], items[i]
 }
 
 type inmemoryBlock struct {
 	commonPrefix []byte
 	data         []byte
-	items        byteSliceSorter
+	items        []Item
 }
 
 func (ib *inmemoryBlock) SizeBytes() int {
-	return int(unsafe.Sizeof(*ib)) + cap(ib.commonPrefix) + cap(ib.data) + cap(ib.items)*int(unsafe.Sizeof([]byte{}))
+	return int(unsafe.Sizeof(*ib)) + cap(ib.commonPrefix) + cap(ib.data) + cap(ib.items)*int(unsafe.Sizeof(Item{}))
 }
 
 func (ib *inmemoryBlock) Reset() {
 	ib.commonPrefix = ib.commonPrefix[:0]
 	ib.data = ib.data[:0]
-
-	items := ib.items
-	for i := range items {
-		// Remove reference to by slice, so GC could free the byte slice.
-		items[i] = nil
-	}
 	ib.items = ib.items[:0]
 }
 
@@ -50,12 +78,14 @@ func (ib *inmemoryBlock) updateCommonPrefix() {
 	if len(ib.items) == 0 {
 		return
 	}
-	cp := ib.items[0]
+	items := ib.items
+	data := ib.data
+	cp := items[0].Bytes(data)
 	if len(cp) == 0 {
 		return
 	}
-	for _, item := range ib.items[1:] {
-		cpLen := commonPrefixLen(cp, item)
+	for _, it := range items[1:] {
+		cpLen := commonPrefixLen(cp, it.Bytes(data))
 		if cpLen == 0 {
 			return
 		}
@@ -82,15 +112,21 @@ func commonPrefixLen(a, b []byte) int {
 //
 // false is returned if x isn't added to ib due to block size contraints.
 func (ib *inmemoryBlock) Add(x []byte) bool {
-	if len(x)+len(ib.data) > maxInmemoryBlockSize {
+	data := ib.data
+	if len(x)+len(data) > maxInmemoryBlockSize {
 		return false
 	}
-	if cap(ib.data) < maxInmemoryBlockSize {
-		dataLen := len(ib.data)
-		ib.data = bytesutil.Resize(ib.data, maxInmemoryBlockSize)[:dataLen]
+	if cap(data) < maxInmemoryBlockSize {
+		dataLen := len(data)
+		data = bytesutil.Resize(data, maxInmemoryBlockSize)[:dataLen]
 	}
-	ib.data = append(ib.data, x...)
-	ib.items = append(ib.items, ib.data[len(ib.data)-len(x):])
+	dataLen := len(data)
+	data = append(data, x...)
+	ib.items = append(ib.items, Item{
+		Start: uint32(dataLen),
+		End:   uint32(len(data)),
+	})
+	ib.data = data
 	return true
 }
 
@@ -100,16 +136,21 @@ func (ib *inmemoryBlock) Add(x []byte) bool {
 const maxInmemoryBlockSize = 64 * 1024
 
 func (ib *inmemoryBlock) sort() {
-	// Use sort.Sort instead of sort.Slice in order to eliminate memory allocation.
-	sort.Sort(&ib.items)
+	sort.Sort(ib)
+	data := ib.data
+	items := ib.items
 	bb := bbPool.Get()
-	b := bytesutil.Resize(bb.B, len(ib.data))
+	b := bytesutil.Resize(bb.B, len(data))
 	b = b[:0]
-	for i, item := range ib.items {
-		b = append(b, item...)
-		ib.items[i] = b[len(b)-len(item):]
+	for i, it := range items {
+		bLen := len(b)
+		b = append(b, it.String(data)...)
+		items[i] = Item{
+			Start: uint32(bLen),
+			End:   uint32(len(b)),
+		}
 	}
-	bb.B, ib.data = ib.data, b
+	bb.B, ib.data = data, b
 	bbPool.Put(bb)
 }
 
@@ -140,7 +181,7 @@ func checkMarshalType(mt marshalType) error {
 
 func (ib *inmemoryBlock) isSorted() bool {
 	// Use sort.IsSorted instead of sort.SliceIsSorted in order to eliminate memory allocation.
-	return sort.IsSorted(&ib.items)
+	return sort.IsSorted(ib)
 }
 
 // MarshalUnsortedData marshals unsorted items from ib to sb.
@@ -179,9 +220,11 @@ func (ib *inmemoryBlock) MarshalSortedData(sb *storageBlock, firstItemDst, commo
 
 func (ib *inmemoryBlock) debugItemsString() string {
 	var sb strings.Builder
-	var prevItem []byte
-	for i, item := range ib.items {
-		if string(item) < string(prevItem) {
+	var prevItem string
+	data := ib.data
+	for i, it := range ib.items {
+		item := it.String(data)
+		if item < prevItem {
 			fmt.Fprintf(&sb, "!!! the next item is smaller than the previous item !!!\n")
 		}
 		fmt.Fprintf(&sb, "%05d %X\n", i, item)
@@ -201,7 +244,9 @@ func (ib *inmemoryBlock) marshalData(sb *storageBlock, firstItemDst, commonPrefi
 		logger.Panicf("BUG: the number of items in the block must be smaller than %d; got %d items", uint64(1<<32), len(ib.items))
 	}
 
-	firstItemDst = append(firstItemDst, ib.items[0]...)
+	data := ib.data
+	firstItem := ib.items[0].Bytes(data)
+	firstItemDst = append(firstItemDst, firstItem...)
 	commonPrefixDst = append(commonPrefixDst, ib.commonPrefix...)
 
 	if len(ib.data)-len(ib.commonPrefix)*len(ib.items) < 64 || len(ib.items) < 2 {
@@ -221,10 +266,11 @@ func (ib *inmemoryBlock) marshalData(sb *storageBlock, firstItemDst, commonPrefi
 	defer encoding.PutUint64s(xs)
 
 	cpLen := len(ib.commonPrefix)
-	prevItem := ib.items[0][cpLen:]
+	prevItem := firstItem[cpLen:]
 	prevPrefixLen := uint64(0)
-	for i, item := range ib.items[1:] {
-		item := item[cpLen:]
+	for i, it := range ib.items[1:] {
+		it.Start += uint32(cpLen)
+		item := it.Bytes(data)
 		prefixLen := uint64(commonPrefixLen(prevItem, item))
 		bItems = append(bItems, item[prefixLen:]...)
 		xLen := prefixLen ^ prevPrefixLen
@@ -240,9 +286,9 @@ func (ib *inmemoryBlock) marshalData(sb *storageBlock, firstItemDst, commonPrefi
 	bbPool.Put(bbItems)
 
 	// Marshal lens data.
-	prevItemLen := uint64(len(ib.items[0]) - cpLen)
-	for i, item := range ib.items[1:] {
-		itemLen := uint64(len(item) - cpLen)
+	prevItemLen := uint64(len(firstItem) - cpLen)
+	for i, it := range ib.items[1:] {
+		itemLen := uint64(int(it.End-it.Start) - cpLen)
 		xLen := itemLen ^ prevItemLen
 		prevItemLen = itemLen
 
@@ -346,11 +392,15 @@ func (ib *inmemoryBlock) UnmarshalData(sb *storageBlock, firstItem, commonPrefix
 	}
 	data := bytesutil.Resize(ib.data, maxInmemoryBlockSize)
 	if n := int(itemsCount) - cap(ib.items); n > 0 {
-		ib.items = append(ib.items[:cap(ib.items)], make([][]byte, n)...)
+		ib.items = append(ib.items[:cap(ib.items)], make([]Item, n)...)
 	}
 	ib.items = ib.items[:itemsCount]
 	data = append(data[:0], firstItem...)
-	ib.items[0] = data
+	items := ib.items
+	items[0] = Item{
+		Start: 0,
+		End:   uint32(len(data)),
+	}
 	prevItem := data[len(commonPrefix):]
 	b := bb.B
 	for i := 1; i < int(itemsCount); i++ {
@@ -363,17 +413,19 @@ func (ib *inmemoryBlock) UnmarshalData(sb *storageBlock, firstItem, commonPrefix
 		if uint64(len(b)) < suffixLen {
 			return fmt.Errorf("not enough data for decoding item from itemsData; want %d bytes; remained %d bytes", suffixLen, len(b))
 		}
-		data = append(data, commonPrefix...)
-
 		if prefixLen > uint64(len(prevItem)) {
 			return fmt.Errorf("prefixLen cannot exceed %d; got %d", len(prevItem), prefixLen)
 		}
+		dataLen := len(data)
+		data = append(data, commonPrefix...)
 		data = append(data, prevItem[:prefixLen]...)
 		data = append(data, b[:suffixLen]...)
-		item := data[len(data)-int(itemLen)-len(commonPrefix):]
-		ib.items[i] = item
+		items[i] = Item{
+			Start: uint32(dataLen),
+			End:   uint32(len(data)),
+		}
 		b = b[suffixLen:]
-		prevItem = item[len(commonPrefix):]
+		prevItem = data[len(data)-int(itemLen):]
 	}
 	if len(b) > 0 {
 		return fmt.Errorf("unexpected tail left after itemsData with len %d: %q", len(b), b)
@@ -381,30 +433,33 @@ func (ib *inmemoryBlock) UnmarshalData(sb *storageBlock, firstItem, commonPrefix
 	if uint64(len(data)) != dataLen {
 		return fmt.Errorf("unexpected data len; got %d; want %d", len(data), dataLen)
 	}
+	ib.data = data
 	if !ib.isSorted() {
 		return fmt.Errorf("decoded data block contains unsorted items; items:\n%s", ib.debugItemsString())
 	}
-	ib.data = data
 	return nil
 }
 
 var bbPool bytesutil.ByteBufferPool
 
 func (ib *inmemoryBlock) marshalDataPlain(sb *storageBlock) {
+	data := ib.data
+
 	// Marshal items data.
 	// There is no need in marshaling the first item, since it is returned
 	// to the caller in marshalData.
 	cpLen := len(ib.commonPrefix)
 	b := sb.itemsData[:0]
-	for _, item := range ib.items[1:] {
-		b = append(b, item[cpLen:]...)
+	for _, it := range ib.items[1:] {
+		it.Start += uint32(cpLen)
+		b = append(b, it.String(data)...)
 	}
 	sb.itemsData = b
 
 	// Marshal length data.
 	b = sb.lensData[:0]
-	for _, item := range ib.items[1:] {
-		b = encoding.MarshalUint64(b, uint64(len(item)-cpLen))
+	for _, it := range ib.items[1:] {
+		b = encoding.MarshalUint64(b, uint64(int(it.End-it.Start)-cpLen))
 	}
 	sb.lensData = b
 }
@@ -431,26 +486,34 @@ func (ib *inmemoryBlock) unmarshalDataPlain(sb *storageBlock, firstItem []byte,
 	}
 
 	// Unmarshal items data.
-	ib.data = bytesutil.Resize(ib.data, len(firstItem)+len(sb.itemsData)+len(commonPrefix)*int(itemsCount))
-	ib.data = append(ib.data[:0], firstItem...)
-	ib.items = append(ib.items[:0], ib.data)
-
+	data := ib.data
+	items := ib.items
+	data = bytesutil.Resize(data, len(firstItem)+len(sb.itemsData)+len(commonPrefix)*int(itemsCount))
+	data = append(data[:0], firstItem...)
+	items = append(items[:0], Item{
+		Start: 0,
+		End:   uint32(len(data)),
+	})
 	b = sb.itemsData
 	for i := 1; i < int(itemsCount); i++ {
 		itemLen := lb.lens[i]
 		if uint64(len(b)) < itemLen {
 			return fmt.Errorf("not enough data for decoding item from itemsData; want %d bytes; remained %d bytes", itemLen, len(b))
 		}
-		ib.data = append(ib.data, commonPrefix...)
-		ib.data = append(ib.data, b[:itemLen]...)
-		item := ib.data[len(ib.data)-int(itemLen)-len(commonPrefix):]
-		ib.items = append(ib.items, item)
+		dataLen := len(data)
+		data = append(data, commonPrefix...)
+		data = append(data, b[:itemLen]...)
+		items = append(items, Item{
+			Start: uint32(dataLen),
+			End:   uint32(len(data)),
+		})
 		b = b[itemLen:]
 	}
+	ib.data = data
+	ib.items = items
 	if len(b) > 0 {
 		return fmt.Errorf("unexpected tail left after itemsData with len %d: %q", len(b), b)
 	}
-
 	return nil
 }
 
diff --git a/lib/mergeset/inmemory_part.go b/lib/mergeset/inmemory_part.go
index 8297ec5f1..e2f4f02bd 100644
--- a/lib/mergeset/inmemory_part.go
+++ b/lib/mergeset/inmemory_part.go
@@ -56,8 +56,8 @@ func (ip *inmemoryPart) Init(ib *inmemoryBlock) {
 
 	ip.ph.itemsCount = uint64(len(ib.items))
 	ip.ph.blocksCount = 1
-	ip.ph.firstItem = append(ip.ph.firstItem[:0], ib.items[0]...)
-	ip.ph.lastItem = append(ip.ph.lastItem[:0], ib.items[len(ib.items)-1]...)
+	ip.ph.firstItem = append(ip.ph.firstItem[:0], ib.items[0].String(ib.data)...)
+	ip.ph.lastItem = append(ip.ph.lastItem[:0], ib.items[len(ib.items)-1].String(ib.data)...)
 
 	fs.MustWriteData(&ip.itemsData, ip.sb.itemsData)
 	ip.bh.itemsBlockOffset = 0
diff --git a/lib/mergeset/merge.go b/lib/mergeset/merge.go
index 276354e8d..7f2fa5ccf 100644
--- a/lib/mergeset/merge.go
+++ b/lib/mergeset/merge.go
@@ -16,7 +16,7 @@ import (
 //
 // The callback must return sorted items. The first and the last item must be unchanged.
 // The callback can re-use data and items for storing the result.
-type PrepareBlockCallback func(data []byte, items [][]byte) ([]byte, [][]byte)
+type PrepareBlockCallback func(data []byte, items []Item) ([]byte, []Item)
 
 // mergeBlockStreams merges bsrs and writes result to bsw.
 //
@@ -122,8 +122,10 @@ again:
 		nextItem = bsm.bsrHeap[0].bh.firstItem
 		hasNextItem = true
 	}
+	items := bsr.Block.items
+	data := bsr.Block.data
 	for bsr.blockItemIdx < len(bsr.Block.items) {
-		item := bsr.Block.items[bsr.blockItemIdx]
+		item := items[bsr.blockItemIdx].Bytes(data)
 		if hasNextItem && string(item) > string(nextItem) {
 			break
 		}
@@ -148,32 +150,36 @@ again:
 
 	// The next item in the bsr.Block exceeds nextItem.
 	// Adjust bsr.bh.firstItem and return bsr to heap.
-	bsr.bh.firstItem = append(bsr.bh.firstItem[:0], bsr.Block.items[bsr.blockItemIdx]...)
+	bsr.bh.firstItem = append(bsr.bh.firstItem[:0], bsr.Block.items[bsr.blockItemIdx].String(bsr.Block.data)...)
 	heap.Push(&bsm.bsrHeap, bsr)
 	goto again
 }
 
 func (bsm *blockStreamMerger) flushIB(bsw *blockStreamWriter, ph *partHeader, itemsMerged *uint64) {
-	if len(bsm.ib.items) == 0 {
+	items := bsm.ib.items
+	data := bsm.ib.data
+	if len(items) == 0 {
 		// Nothing to flush.
 		return
 	}
-	atomic.AddUint64(itemsMerged, uint64(len(bsm.ib.items)))
+	atomic.AddUint64(itemsMerged, uint64(len(items)))
 	if bsm.prepareBlock != nil {
-		bsm.firstItem = append(bsm.firstItem[:0], bsm.ib.items[0]...)
-		bsm.lastItem = append(bsm.lastItem[:0], bsm.ib.items[len(bsm.ib.items)-1]...)
-		bsm.ib.data, bsm.ib.items = bsm.prepareBlock(bsm.ib.data, bsm.ib.items)
-		if len(bsm.ib.items) == 0 {
+		bsm.firstItem = append(bsm.firstItem[:0], items[0].String(data)...)
+		bsm.lastItem = append(bsm.lastItem[:0], items[len(items)-1].String(data)...)
+		data, items = bsm.prepareBlock(data, items)
+		bsm.ib.data = data
+		bsm.ib.items = items
+		if len(items) == 0 {
 			// Nothing to flush
 			return
 		}
 		// Consistency checks after prepareBlock call.
-		firstItem := bsm.ib.items[0]
-		if string(firstItem) != string(bsm.firstItem) {
+		firstItem := items[0].String(data)
+		if firstItem != string(bsm.firstItem) {
 			logger.Panicf("BUG: prepareBlock must return first item equal to the original first item;\ngot\n%X\nwant\n%X", firstItem, bsm.firstItem)
 		}
-		lastItem := bsm.ib.items[len(bsm.ib.items)-1]
-		if string(lastItem) != string(bsm.lastItem) {
+		lastItem := items[len(items)-1].String(data)
+		if lastItem != string(bsm.lastItem) {
 			logger.Panicf("BUG: prepareBlock must return last item equal to the original last item;\ngot\n%X\nwant\n%X", lastItem, bsm.lastItem)
 		}
 		// Verify whether the bsm.ib.items are sorted only in tests, since this
@@ -182,12 +188,12 @@ func (bsm *blockStreamMerger) flushIB(bsw *blockStreamWriter, ph *partHeader, it
 			logger.Panicf("BUG: prepareBlock must return sorted items;\ngot\n%s", bsm.ib.debugItemsString())
 		}
 	}
-	ph.itemsCount += uint64(len(bsm.ib.items))
+	ph.itemsCount += uint64(len(items))
 	if !bsm.phFirstItemCaught {
-		ph.firstItem = append(ph.firstItem[:0], bsm.ib.items[0]...)
+		ph.firstItem = append(ph.firstItem[:0], items[0].String(data)...)
 		bsm.phFirstItemCaught = true
 	}
-	ph.lastItem = append(ph.lastItem[:0], bsm.ib.items[len(bsm.ib.items)-1]...)
+	ph.lastItem = append(ph.lastItem[:0], items[len(items)-1].String(data)...)
 	bsw.WriteBlock(&bsm.ib)
 	bsm.ib.Reset()
 	ph.blocksCount++
diff --git a/lib/mergeset/part.go b/lib/mergeset/part.go
index 2b01d94be..7da60ceb3 100644
--- a/lib/mergeset/part.go
+++ b/lib/mergeset/part.go
@@ -138,24 +138,14 @@ type indexBlock struct {
 }
 
 func (idxb *indexBlock) SizeBytes() int {
-	return cap(idxb.bhs) * int(unsafe.Sizeof(blockHeader{}))
-}
-
-func getIndexBlock() *indexBlock {
-	v := indexBlockPool.Get()
-	if v == nil {
-		return &indexBlock{}
+	bhs := idxb.bhs[:cap(idxb.bhs)]
+	n := int(unsafe.Sizeof(*idxb))
+	for i := range bhs {
+		n += bhs[i].SizeBytes()
 	}
-	return v.(*indexBlock)
+	return n
 }
 
-func putIndexBlock(idxb *indexBlock) {
-	idxb.bhs = idxb.bhs[:0]
-	indexBlockPool.Put(idxb)
-}
-
-var indexBlockPool sync.Pool
-
 type indexBlockCache struct {
 	// Atomically updated counters must go first in the struct, so they are properly
 	// aligned to 8 bytes on 32-bit architectures.
@@ -194,12 +184,6 @@ func newIndexBlockCache() *indexBlockCache {
 func (idxbc *indexBlockCache) MustClose() {
 	close(idxbc.cleanerStopCh)
 	idxbc.cleanerWG.Wait()
-
-	// It is safe returning idxbc.m to pool, since the MustClose can be called
-	// when the idxbc entries are no longer accessed by concurrent goroutines.
-	for _, idxbe := range idxbc.m {
-		putIndexBlock(idxbe.idxb)
-	}
 	idxbc.m = nil
 }
 
@@ -223,8 +207,6 @@ func (idxbc *indexBlockCache) cleanByTimeout() {
 	for k, idxbe := range idxbc.m {
 		// Delete items accessed more than two minutes ago.
 		if currentTime-atomic.LoadUint64(&idxbe.lastAccessTime) > 2*60 {
-			// do not call putIndexBlock(ibxbc.m[k]), since it
-			// may be used by concurrent goroutines.
 			delete(idxbc.m, k)
 		}
 	}
@@ -257,8 +239,6 @@ func (idxbc *indexBlockCache) Put(k uint64, idxb *indexBlock) {
 		// Remove 10% of items from the cache.
 		overflow = int(float64(len(idxbc.m)) * 0.1)
 		for k := range idxbc.m {
-			// do not call putIndexBlock(ibxbc.m[k]), since it
-			// may be used by concurrent goroutines.
 			delete(idxbc.m, k)
 			overflow--
 			if overflow == 0 {
@@ -348,12 +328,6 @@ func newInmemoryBlockCache() *inmemoryBlockCache {
 func (ibc *inmemoryBlockCache) MustClose() {
 	close(ibc.cleanerStopCh)
 	ibc.cleanerWG.Wait()
-
-	// It is safe returning ibc.m entries to pool, since the MustClose can be called
-	// only if no other goroutines access ibc entries.
-	for _, ibe := range ibc.m {
-		putInmemoryBlock(ibe.ib)
-	}
 	ibc.m = nil
 }
 
@@ -377,8 +351,6 @@ func (ibc *inmemoryBlockCache) cleanByTimeout() {
 	for k, ibe := range ibc.m {
 		// Delete items accessed more than a two minutes ago.
 		if currentTime-atomic.LoadUint64(&ibe.lastAccessTime) > 2*60 {
-			// do not call putInmemoryBlock(ibc.m[k]), since it
-			// may be used by concurrent goroutines.
 			delete(ibc.m, k)
 		}
 	}
@@ -412,8 +384,6 @@ func (ibc *inmemoryBlockCache) Put(k inmemoryBlockCacheKey, ib *inmemoryBlock) {
 		// Remove 10% of items from the cache.
 		overflow = int(float64(len(ibc.m)) * 0.1)
 		for k := range ibc.m {
-			// do not call putInmemoryBlock(ib), since the ib
-			// may be used by concurrent goroutines.
 			delete(ibc.m, k)
 			overflow--
 			if overflow == 0 {
diff --git a/lib/mergeset/part_search.go b/lib/mergeset/part_search.go
index 0c7b17855..8671561b6 100644
--- a/lib/mergeset/part_search.go
+++ b/lib/mergeset/part_search.go
@@ -142,14 +142,17 @@ func (ps *partSearch) Seek(k []byte) {
 
 	// Locate the first item to scan in the block.
 	items := ps.ib.items
+	data := ps.ib.data
 	cpLen := commonPrefixLen(ps.ib.commonPrefix, k)
 	if cpLen > 0 {
 		keySuffix := k[cpLen:]
 		ps.ibItemIdx = sort.Search(len(items), func(i int) bool {
-			return string(keySuffix) <= string(items[i][cpLen:])
+			it := items[i]
+			it.Start += uint32(cpLen)
+			return string(keySuffix) <= it.String(data)
 		})
 	} else {
-		ps.ibItemIdx = binarySearchKey(items, k)
+		ps.ibItemIdx = binarySearchKey(data, items, k)
 	}
 	if ps.ibItemIdx < len(items) {
 		// The item has been found.
@@ -168,13 +171,14 @@ func (ps *partSearch) tryFastSeek(k []byte) bool {
 	if ps.ib == nil {
 		return false
 	}
+	data := ps.ib.data
 	items := ps.ib.items
 	idx := ps.ibItemIdx
 	if idx >= len(items) {
 		// The ib is exhausted.
 		return false
 	}
-	if string(k) > string(items[len(items)-1]) {
+	if string(k) > items[len(items)-1].String(data) {
 		// The item is located in next blocks.
 		return false
 	}
@@ -183,8 +187,8 @@ func (ps *partSearch) tryFastSeek(k []byte) bool {
 	if idx > 0 {
 		idx--
 	}
-	if string(k) < string(items[idx]) {
-		if string(k) < string(items[0]) {
+	if string(k) < items[idx].String(data) {
+		if string(k) < items[0].String(data) {
 			// The item is located in previous blocks.
 			return false
 		}
@@ -192,7 +196,7 @@ func (ps *partSearch) tryFastSeek(k []byte) bool {
 	}
 
 	// The item is located in the current block
-	ps.ibItemIdx = idx + binarySearchKey(items[idx:], k)
+	ps.ibItemIdx = idx + binarySearchKey(data, items[idx:], k)
 	return true
 }
 
@@ -204,10 +208,11 @@ func (ps *partSearch) NextItem() bool {
 		return false
 	}
 
-	if ps.ibItemIdx < len(ps.ib.items) {
+	items := ps.ib.items
+	if ps.ibItemIdx < len(items) {
 		// Fast path - the current block contains more items.
 		// Proceed to the next item.
-		ps.Item = ps.ib.items[ps.ibItemIdx]
+		ps.Item = items[ps.ibItemIdx].Bytes(ps.ib.data)
 		ps.ibItemIdx++
 		return true
 	}
@@ -219,7 +224,7 @@ func (ps *partSearch) NextItem() bool {
 	}
 
 	// Invariant: len(ps.ib.items) > 0 after nextBlock.
-	ps.Item = ps.ib.items[0]
+	ps.Item = ps.ib.items[0].Bytes(ps.ib.data)
 	ps.ibItemIdx++
 	return true
 }
@@ -279,7 +284,7 @@ func (ps *partSearch) readIndexBlock(mr *metaindexRow) (*indexBlock, error) {
 	if err != nil {
 		return nil, fmt.Errorf("cannot decompress index block: %w", err)
 	}
-	idxb := getIndexBlock()
+	idxb := &indexBlock{}
 	idxb.bhs, err = unmarshalBlockHeaders(idxb.bhs[:0], ps.indexBuf, int(mr.blockHeadersCount))
 	if err != nil {
 		return nil, fmt.Errorf("cannot unmarshal block headers from index block (offset=%d, size=%d): %w", mr.indexBlockOffset, mr.indexBlockSize, err)
@@ -319,11 +324,11 @@ func (ps *partSearch) readInmemoryBlock(bh *blockHeader) (*inmemoryBlock, error)
 	return ib, nil
 }
 
-func binarySearchKey(items [][]byte, key []byte) int {
+func binarySearchKey(data []byte, items []Item, key []byte) int {
 	if len(items) == 0 {
 		return 0
 	}
-	if string(key) <= string(items[0]) {
+	if string(key) <= items[0].String(data) {
 		// Fast path - the item is the first.
 		return 0
 	}
@@ -335,7 +340,7 @@ func binarySearchKey(items [][]byte, key []byte) int {
 	i, j := uint(0), n
 	for i < j {
 		h := uint(i+j) >> 1
-		if h >= 0 && h < uint(len(items)) && string(key) > string(items[h]) {
+		if h >= 0 && h < uint(len(items)) && string(key) > items[h].String(data) {
 			i = h + 1
 		} else {
 			j = h
diff --git a/lib/mergeset/table.go b/lib/mergeset/table.go
index d66639253..ac57e975d 100644
--- a/lib/mergeset/table.go
+++ b/lib/mergeset/table.go
@@ -1333,12 +1333,6 @@ func appendPartsToMerge(dst, src []*partWrapper, maxPartsToMerge int, maxItems u
 			for _, pw := range a {
 				itemsSum += pw.p.ph.itemsCount
 			}
-			if itemsSum < 1e6 && len(a) < maxPartsToMerge {
-				// Do not merge parts with too small number of items if the number of source parts
-				// isn't equal to maxPartsToMerge. This should reduce CPU usage and disk IO usage
-				// for small parts merge.
-				continue
-			}
 			if itemsSum > maxItems {
 				// There is no sense in checking the remaining bigger parts.
 				break
diff --git a/lib/promrelabel/config.go b/lib/promrelabel/config.go
index abd9419a4..29d624174 100644
--- a/lib/promrelabel/config.go
+++ b/lib/promrelabel/config.go
@@ -23,38 +23,78 @@ type RelabelConfig struct {
 	Action       string   `yaml:"action,omitempty"`
 }
 
+// ParsedConfigs represents parsed relabel configs.
+type ParsedConfigs struct {
+	prcs []*parsedRelabelConfig
+}
+
+// Len returns the number of relabel configs in pcs.
+func (pcs *ParsedConfigs) Len() int {
+	if pcs == nil {
+		return 0
+	}
+	return len(pcs.prcs)
+}
+
+// String returns human-readabale representation for pcs.
+func (pcs *ParsedConfigs) String() string {
+	if pcs == nil {
+		return ""
+	}
+	var sb strings.Builder
+	for _, prc := range pcs.prcs {
+		fmt.Fprintf(&sb, "%s", prc.String())
+	}
+	return sb.String()
+}
+
 // LoadRelabelConfigs loads relabel configs from the given path.
-func LoadRelabelConfigs(path string) ([]ParsedRelabelConfig, error) {
+func LoadRelabelConfigs(path string) (*ParsedConfigs, error) {
 	data, err := ioutil.ReadFile(path)
 	if err != nil {
 		return nil, fmt.Errorf("cannot read `relabel_configs` from %q: %w", path, err)
 	}
 	data = envtemplate.Replace(data)
+	pcs, err := ParseRelabelConfigsData(data)
+	if err != nil {
+		return nil, fmt.Errorf("cannot unmarshal `relabel_configs` from %q: %w", path, err)
+	}
+	return pcs, nil
+}
+
+// ParseRelabelConfigsData parses relabel configs from the given data.
+func ParseRelabelConfigsData(data []byte) (*ParsedConfigs, error) {
 	var rcs []RelabelConfig
 	if err := yaml.UnmarshalStrict(data, &rcs); err != nil {
-		return nil, fmt.Errorf("cannot unmarshal `relabel_configs` from %q: %w", path, err)
+		return nil, err
 	}
-	return ParseRelabelConfigs(nil, rcs)
+	return ParseRelabelConfigs(rcs)
 }
 
 // ParseRelabelConfigs parses rcs to dst.
-func ParseRelabelConfigs(dst []ParsedRelabelConfig, rcs []RelabelConfig) ([]ParsedRelabelConfig, error) {
+func ParseRelabelConfigs(rcs []RelabelConfig) (*ParsedConfigs, error) {
 	if len(rcs) == 0 {
-		return dst, nil
+		return nil, nil
 	}
+	prcs := make([]*parsedRelabelConfig, len(rcs))
 	for i := range rcs {
-		var err error
-		dst, err = parseRelabelConfig(dst, &rcs[i])
+		prc, err := parseRelabelConfig(&rcs[i])
 		if err != nil {
-			return dst, fmt.Errorf("error when parsing `relabel_config` #%d: %w", i+1, err)
+			return nil, fmt.Errorf("error when parsing `relabel_config` #%d: %w", i+1, err)
 		}
+		prcs[i] = prc
 	}
-	return dst, nil
+	return &ParsedConfigs{
+		prcs: prcs,
+	}, nil
 }
 
-var defaultRegexForRelabelConfig = regexp.MustCompile("^(.*)$")
+var (
+	defaultOriginalRegexForRelabelConfig = regexp.MustCompile(".*")
+	defaultRegexForRelabelConfig         = regexp.MustCompile("^(.*)$")
+)
 
-func parseRelabelConfig(dst []ParsedRelabelConfig, rc *RelabelConfig) ([]ParsedRelabelConfig, error) {
+func parseRelabelConfig(rc *RelabelConfig) (*parsedRelabelConfig, error) {
 	sourceLabels := rc.SourceLabels
 	separator := ";"
 	if rc.Separator != nil {
@@ -62,6 +102,7 @@ func parseRelabelConfig(dst []ParsedRelabelConfig, rc *RelabelConfig) ([]ParsedR
 	}
 	targetLabel := rc.TargetLabel
 	regexCompiled := defaultRegexForRelabelConfig
+	regexOriginalCompiled := defaultOriginalRegexForRelabelConfig
 	if rc.Regex != nil {
 		regex := *rc.Regex
 		if rc.Action != "replace_all" && rc.Action != "labelmap_all" {
@@ -69,9 +110,14 @@ func parseRelabelConfig(dst []ParsedRelabelConfig, rc *RelabelConfig) ([]ParsedR
 		}
 		re, err := regexp.Compile(regex)
 		if err != nil {
-			return dst, fmt.Errorf("cannot parse `regex` %q: %w", regex, err)
+			return nil, fmt.Errorf("cannot parse `regex` %q: %w", regex, err)
 		}
 		regexCompiled = re
+		reOriginal, err := regexp.Compile(*rc.Regex)
+		if err != nil {
+			return nil, fmt.Errorf("cannot parse `regex` %q: %w", *rc.Regex, err)
+		}
+		regexOriginalCompiled = reOriginal
 	}
 	modulus := rc.Modulus
 	replacement := "$1"
@@ -85,49 +131,49 @@ func parseRelabelConfig(dst []ParsedRelabelConfig, rc *RelabelConfig) ([]ParsedR
 	switch action {
 	case "replace":
 		if targetLabel == "" {
-			return dst, fmt.Errorf("missing `target_label` for `action=replace`")
+			return nil, fmt.Errorf("missing `target_label` for `action=replace`")
 		}
 	case "replace_all":
 		if len(sourceLabels) == 0 {
-			return dst, fmt.Errorf("missing `source_labels` for `action=replace_all`")
+			return nil, fmt.Errorf("missing `source_labels` for `action=replace_all`")
 		}
 		if targetLabel == "" {
-			return dst, fmt.Errorf("missing `target_label` for `action=replace_all`")
+			return nil, fmt.Errorf("missing `target_label` for `action=replace_all`")
 		}
 	case "keep_if_equal":
 		if len(sourceLabels) < 2 {
-			return dst, fmt.Errorf("`source_labels` must contain at least two entries for `action=keep_if_equal`; got %q", sourceLabels)
+			return nil, fmt.Errorf("`source_labels` must contain at least two entries for `action=keep_if_equal`; got %q", sourceLabels)
 		}
 	case "drop_if_equal":
 		if len(sourceLabels) < 2 {
-			return dst, fmt.Errorf("`source_labels` must contain at least two entries for `action=drop_if_equal`; got %q", sourceLabels)
+			return nil, fmt.Errorf("`source_labels` must contain at least two entries for `action=drop_if_equal`; got %q", sourceLabels)
 		}
 	case "keep":
 		if len(sourceLabels) == 0 {
-			return dst, fmt.Errorf("missing `source_labels` for `action=keep`")
+			return nil, fmt.Errorf("missing `source_labels` for `action=keep`")
 		}
 	case "drop":
 		if len(sourceLabels) == 0 {
-			return dst, fmt.Errorf("missing `source_labels` for `action=drop`")
+			return nil, fmt.Errorf("missing `source_labels` for `action=drop`")
 		}
 	case "hashmod":
 		if len(sourceLabels) == 0 {
-			return dst, fmt.Errorf("missing `source_labels` for `action=hashmod`")
+			return nil, fmt.Errorf("missing `source_labels` for `action=hashmod`")
 		}
 		if targetLabel == "" {
-			return dst, fmt.Errorf("missing `target_label` for `action=hashmod`")
+			return nil, fmt.Errorf("missing `target_label` for `action=hashmod`")
 		}
 		if modulus < 1 {
-			return dst, fmt.Errorf("unexpected `modulus` for `action=hashmod`: %d; must be greater than 0", modulus)
+			return nil, fmt.Errorf("unexpected `modulus` for `action=hashmod`: %d; must be greater than 0", modulus)
 		}
 	case "labelmap":
 	case "labelmap_all":
 	case "labeldrop":
 	case "labelkeep":
 	default:
-		return dst, fmt.Errorf("unknown `action` %q", action)
+		return nil, fmt.Errorf("unknown `action` %q", action)
 	}
-	dst = append(dst, ParsedRelabelConfig{
+	return &parsedRelabelConfig{
 		SourceLabels: sourceLabels,
 		Separator:    separator,
 		TargetLabel:  targetLabel,
@@ -136,8 +182,8 @@ func parseRelabelConfig(dst []ParsedRelabelConfig, rc *RelabelConfig) ([]ParsedR
 		Replacement:  replacement,
 		Action:       action,
 
+		regexOriginal:                regexOriginalCompiled,
 		hasCaptureGroupInTargetLabel: strings.Contains(targetLabel, "$"),
 		hasCaptureGroupInReplacement: strings.Contains(replacement, "$"),
-	})
-	return dst, nil
+	}, nil
 }
diff --git a/lib/promrelabel/relabel.go b/lib/promrelabel/relabel.go
index 0a7a02646..a161e55cc 100644
--- a/lib/promrelabel/relabel.go
+++ b/lib/promrelabel/relabel.go
@@ -12,10 +12,10 @@ import (
 	xxhash "github.com/cespare/xxhash/v2"
 )
 
-// ParsedRelabelConfig contains parsed `relabel_config`.
+// parsedRelabelConfig contains parsed `relabel_config`.
 //
 // See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config
-type ParsedRelabelConfig struct {
+type parsedRelabelConfig struct {
 	SourceLabels []string
 	Separator    string
 	TargetLabel  string
@@ -24,29 +24,32 @@ type ParsedRelabelConfig struct {
 	Replacement  string
 	Action       string
 
+	regexOriginal                *regexp.Regexp
 	hasCaptureGroupInTargetLabel bool
 	hasCaptureGroupInReplacement bool
 }
 
 // String returns human-readable representation for prc.
-func (prc *ParsedRelabelConfig) String() string {
+func (prc *parsedRelabelConfig) String() string {
 	return fmt.Sprintf("SourceLabels=%s, Separator=%s, TargetLabel=%s, Regex=%s, Modulus=%d, Replacement=%s, Action=%s",
 		prc.SourceLabels, prc.Separator, prc.TargetLabel, prc.Regex.String(), prc.Modulus, prc.Replacement, prc.Action)
 }
 
-// ApplyRelabelConfigs applies prcs to labels starting from the labelsOffset.
+// Apply applies pcs to labels starting from the labelsOffset.
 //
 // If isFinalize is set, then FinalizeLabels is called on the labels[labelsOffset:].
 //
 // The returned labels at labels[labelsOffset:] are sorted.
-func ApplyRelabelConfigs(labels []prompbmarshal.Label, labelsOffset int, prcs []ParsedRelabelConfig, isFinalize bool) []prompbmarshal.Label {
-	for i := range prcs {
-		tmp := applyRelabelConfig(labels, labelsOffset, &prcs[i])
-		if len(tmp) == labelsOffset {
-			// All the labels have been removed.
-			return tmp
+func (pcs *ParsedConfigs) Apply(labels []prompbmarshal.Label, labelsOffset int, isFinalize bool) []prompbmarshal.Label {
+	if pcs != nil {
+		for _, prc := range pcs.prcs {
+			tmp := prc.apply(labels, labelsOffset)
+			if len(tmp) == labelsOffset {
+				// All the labels have been removed.
+				return tmp
+			}
+			labels = tmp
 		}
-		labels = tmp
 	}
 	labels = removeEmptyLabels(labels, labelsOffset)
 	if isFinalize {
@@ -106,21 +109,32 @@ func FinalizeLabels(dst, src []prompbmarshal.Label) []prompbmarshal.Label {
 	return dst
 }
 
-// applyRelabelConfig applies relabeling according to prc.
+// apply applies relabeling according to prc.
 //
 // See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config
-func applyRelabelConfig(labels []prompbmarshal.Label, labelsOffset int, prc *ParsedRelabelConfig) []prompbmarshal.Label {
+func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset int) []prompbmarshal.Label {
 	src := labels[labelsOffset:]
 	switch prc.Action {
 	case "replace":
 		bb := relabelBufPool.Get()
 		bb.B = concatLabelValues(bb.B[:0], src, prc.SourceLabels, prc.Separator)
-		if len(bb.B) == 0 && prc.Regex == defaultRegexForRelabelConfig && !prc.hasCaptureGroupInReplacement && !prc.hasCaptureGroupInTargetLabel {
-			// Fast path for the following rule that just sets label value:
-			// - target_label: foobar
-			//   replacement: something-here
-			relabelBufPool.Put(bb)
-			return setLabelValue(labels, labelsOffset, prc.TargetLabel, prc.Replacement)
+		if prc.Regex == defaultRegexForRelabelConfig && !prc.hasCaptureGroupInTargetLabel {
+			if prc.Replacement == "$1" {
+				// Fast path for the rule that copies source label values to destination:
+				// - source_labels: [...]
+				//   target_label: foobar
+				valueStr := string(bb.B)
+				relabelBufPool.Put(bb)
+				return setLabelValue(labels, labelsOffset, prc.TargetLabel, valueStr)
+			}
+			if !prc.hasCaptureGroupInReplacement {
+				// Fast path for the rule that sets label value:
+				// - target_label: foobar
+				//   replacement: something-here
+				relabelBufPool.Put(bb)
+				labels = setLabelValue(labels, labelsOffset, prc.TargetLabel, prc.Replacement)
+				return labels
+			}
 		}
 		match := prc.Regex.FindSubmatchIndex(bb.B)
 		if match == nil {
@@ -139,15 +153,13 @@ func applyRelabelConfig(labels []prompbmarshal.Label, labelsOffset int, prc *Par
 	case "replace_all":
 		bb := relabelBufPool.Get()
 		bb.B = concatLabelValues(bb.B[:0], src, prc.SourceLabels, prc.Separator)
-		if !prc.Regex.Match(bb.B) {
-			// Fast path - nothing to replace.
-			relabelBufPool.Put(bb)
-			return labels
-		}
-		sourceStr := string(bb.B) // Make a copy of bb, since it can be returned from ReplaceAllString
+		sourceStr := string(bb.B)
 		relabelBufPool.Put(bb)
-		valueStr := prc.Regex.ReplaceAllString(sourceStr, prc.Replacement)
-		return setLabelValue(labels, labelsOffset, prc.TargetLabel, valueStr)
+		valueStr, ok := prc.replaceStringSubmatches(sourceStr, prc.Replacement, prc.hasCaptureGroupInReplacement)
+		if ok {
+			labels = setLabelValue(labels, labelsOffset, prc.TargetLabel, valueStr)
+		}
+		return labels
 	case "keep_if_equal":
 		// Keep the entry if all the label values in source_labels are equal.
 		// For example:
@@ -175,7 +187,7 @@ func applyRelabelConfig(labels []prompbmarshal.Label, labelsOffset int, prc *Par
 	case "keep":
 		bb := relabelBufPool.Get()
 		bb.B = concatLabelValues(bb.B[:0], src, prc.SourceLabels, prc.Separator)
-		keep := prc.Regex.Match(bb.B)
+		keep := prc.matchString(bytesutil.ToUnsafeString(bb.B))
 		relabelBufPool.Put(bb)
 		if !keep {
 			return labels[:labelsOffset]
@@ -184,7 +196,7 @@ func applyRelabelConfig(labels []prompbmarshal.Label, labelsOffset int, prc *Par
 	case "drop":
 		bb := relabelBufPool.Get()
 		bb.B = concatLabelValues(bb.B[:0], src, prc.SourceLabels, prc.Separator)
-		drop := prc.Regex.Match(bb.B)
+		drop := prc.matchString(bytesutil.ToUnsafeString(bb.B))
 		relabelBufPool.Put(bb)
 		if drop {
 			return labels[:labelsOffset]
@@ -200,30 +212,23 @@ func applyRelabelConfig(labels []prompbmarshal.Label, labelsOffset int, prc *Par
 	case "labelmap":
 		for i := range src {
 			label := &src[i]
-			match := prc.Regex.FindStringSubmatchIndex(label.Name)
-			if match == nil {
-				continue
+			labelName, ok := prc.replaceFullString(label.Name, prc.Replacement, prc.hasCaptureGroupInReplacement)
+			if ok {
+				labels = setLabelValue(labels, labelsOffset, labelName, label.Value)
 			}
-			value := relabelBufPool.Get()
-			value.B = prc.Regex.ExpandString(value.B[:0], prc.Replacement, label.Name, match)
-			labelName := string(value.B)
-			relabelBufPool.Put(value)
-			labels = setLabelValue(labels, labelsOffset, labelName, label.Value)
 		}
 		return labels
 	case "labelmap_all":
 		for i := range src {
 			label := &src[i]
-			if !prc.Regex.MatchString(label.Name) {
-				continue
-			}
-			label.Name = prc.Regex.ReplaceAllString(label.Name, prc.Replacement)
+			label.Name, _ = prc.replaceStringSubmatches(label.Name, prc.Replacement, prc.hasCaptureGroupInReplacement)
 		}
 		return labels
 	case "labeldrop":
 		keepSrc := true
 		for i := range src {
-			if prc.Regex.MatchString(src[i].Name) {
+			label := &src[i]
+			if prc.matchString(label.Name) {
 				keepSrc = false
 				break
 			}
@@ -234,7 +239,7 @@ func applyRelabelConfig(labels []prompbmarshal.Label, labelsOffset int, prc *Par
 		dst := labels[:labelsOffset]
 		for i := range src {
 			label := &src[i]
-			if !prc.Regex.MatchString(label.Name) {
+			if !prc.matchString(label.Name) {
 				dst = append(dst, *label)
 			}
 		}
@@ -242,7 +247,8 @@ func applyRelabelConfig(labels []prompbmarshal.Label, labelsOffset int, prc *Par
 	case "labelkeep":
 		keepSrc := true
 		for i := range src {
-			if !prc.Regex.MatchString(src[i].Name) {
+			label := &src[i]
+			if !prc.matchString(label.Name) {
 				keepSrc = false
 				break
 			}
@@ -253,7 +259,7 @@ func applyRelabelConfig(labels []prompbmarshal.Label, labelsOffset int, prc *Par
 		dst := labels[:labelsOffset]
 		for i := range src {
 			label := &src[i]
-			if prc.Regex.MatchString(label.Name) {
+			if prc.matchString(label.Name) {
 				dst = append(dst, *label)
 			}
 		}
@@ -264,7 +270,74 @@ func applyRelabelConfig(labels []prompbmarshal.Label, labelsOffset int, prc *Par
 	}
 }
 
-func (prc *ParsedRelabelConfig) expandCaptureGroups(template, source string, match []int) string {
+func (prc *parsedRelabelConfig) replaceFullString(s, replacement string, hasCaptureGroupInReplacement bool) (string, bool) {
+	prefix, complete := prc.regexOriginal.LiteralPrefix()
+	if complete && !hasCaptureGroupInReplacement {
+		if s == prefix {
+			return replacement, true
+		}
+		return s, false
+	}
+	if !strings.HasPrefix(s, prefix) {
+		return s, false
+	}
+	if replacement == "$1" {
+		// Fast path for commonly used rule for deleting label prefixes such as:
+		//
+		// - action: labelmap
+		//   regex: __meta_kubernetes_node_label_(.+)
+		//
+		reStr := prc.regexOriginal.String()
+		if strings.HasPrefix(reStr, prefix) {
+			suffix := s[len(prefix):]
+			reSuffix := reStr[len(prefix):]
+			switch reSuffix {
+			case "(.*)":
+				return suffix, true
+			case "(.+)":
+				if len(suffix) > 0 {
+					return suffix, true
+				}
+				return s, false
+			}
+		}
+	}
+	// Slow path - regexp processing
+	match := prc.Regex.FindStringSubmatchIndex(s)
+	if match == nil {
+		return s, false
+	}
+	bb := relabelBufPool.Get()
+	bb.B = prc.Regex.ExpandString(bb.B[:0], replacement, s, match)
+	result := string(bb.B)
+	relabelBufPool.Put(bb)
+	return result, true
+}
+
+func (prc *parsedRelabelConfig) replaceStringSubmatches(s, replacement string, hasCaptureGroupInReplacement bool) (string, bool) {
+	re := prc.regexOriginal
+	prefix, complete := re.LiteralPrefix()
+	if complete && !hasCaptureGroupInReplacement {
+		if !strings.Contains(s, prefix) {
+			return s, false
+		}
+		return strings.ReplaceAll(s, prefix, replacement), true
+	}
+	if !re.MatchString(s) {
+		return s, false
+	}
+	return re.ReplaceAllString(s, replacement), true
+}
+
+func (prc *parsedRelabelConfig) matchString(s string) bool {
+	prefix, complete := prc.regexOriginal.LiteralPrefix()
+	if complete {
+		return prefix == s
+	}
+	return strings.HasPrefix(s, prefix) && prc.Regex.MatchString(s)
+}
+
+func (prc *parsedRelabelConfig) expandCaptureGroups(template, source string, match []int) string {
 	bb := relabelBufPool.Get()
 	bb.B = prc.Regex.ExpandString(bb.B[:0], template, source, match)
 	s := string(bb.B)
diff --git a/lib/promscrape/config.go b/lib/promscrape/config.go
index aade32faa..4f97506b6 100644
--- a/lib/promscrape/config.go
+++ b/lib/promscrape/config.go
@@ -24,6 +24,7 @@ import (
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/kubernetes"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/openstack"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/proxy"
+	"github.com/VictoriaMetrics/metrics"
 	"gopkg.in/yaml.v2"
 )
 
@@ -481,13 +482,11 @@ func getScrapeWorkConfig(sc *ScrapeConfig, baseDir string, globalCfg *GlobalConf
 	if err != nil {
 		return nil, fmt.Errorf("cannot parse auth config for `job_name` %q: %w", jobName, err)
 	}
-	var relabelConfigs []promrelabel.ParsedRelabelConfig
-	relabelConfigs, err = promrelabel.ParseRelabelConfigs(relabelConfigs[:0], sc.RelabelConfigs)
+	relabelConfigs, err := promrelabel.ParseRelabelConfigs(sc.RelabelConfigs)
 	if err != nil {
 		return nil, fmt.Errorf("cannot parse `relabel_configs` for `job_name` %q: %w", jobName, err)
 	}
-	var metricRelabelConfigs []promrelabel.ParsedRelabelConfig
-	metricRelabelConfigs, err = promrelabel.ParseRelabelConfigs(metricRelabelConfigs[:0], sc.MetricRelabelConfigs)
+	metricRelabelConfigs, err := promrelabel.ParseRelabelConfigs(sc.MetricRelabelConfigs)
 	if err != nil {
 		return nil, fmt.Errorf("cannot parse `metric_relabel_configs` for `job_name` %q: %w", jobName, err)
 	}
@@ -526,8 +525,8 @@ type scrapeWorkConfig struct {
 	honorLabels          bool
 	honorTimestamps      bool
 	externalLabels       map[string]string
-	relabelConfigs       []promrelabel.ParsedRelabelConfig
-	metricRelabelConfigs []promrelabel.ParsedRelabelConfig
+	relabelConfigs       *promrelabel.ParsedConfigs
+	metricRelabelConfigs *promrelabel.ParsedConfigs
 	sampleLimit          int
 	disableCompression   bool
 	disableKeepAlive     bool
@@ -608,6 +607,7 @@ func appendGCEScrapeWork(dst []*ScrapeWork, sdc *gce.SDConfig, swc *scrapeWorkCo
 }
 
 func appendScrapeWorkForTargetLabels(dst []*ScrapeWork, swc *scrapeWorkConfig, targetLabels []map[string]string, sectionName string) []*ScrapeWork {
+	startTime := time.Now()
 	for _, metaLabels := range targetLabels {
 		target := metaLabels["__address__"]
 		var err error
@@ -617,6 +617,7 @@ func appendScrapeWorkForTargetLabels(dst []*ScrapeWork, swc *scrapeWorkConfig, t
 			continue
 		}
 	}
+	metrics.GetOrCreateHistogram(fmt.Sprintf("vm_promscrape_target_relabel_duration_seconds{type=%q}", sectionName)).UpdateDuration(startTime)
 	return dst
 }
 
@@ -692,7 +693,7 @@ func appendScrapeWork(dst []*ScrapeWork, swc *scrapeWorkConfig, target string, e
 		// Reduce memory usage by interning all the strings in originalLabels.
 		internLabelStrings(originalLabels)
 	}
-	labels = promrelabel.ApplyRelabelConfigs(labels, 0, swc.relabelConfigs, false)
+	labels = swc.relabelConfigs.Apply(labels, 0, false)
 	labels = promrelabel.RemoveMetaLabels(labels[:0], labels)
 	// Remove references to already deleted labels, so GC could clean strings for label name and label value past len(labels).
 	// This should reduce memory usage when relabeling creates big number of temporary labels with long names and/or values.
diff --git a/lib/promscrape/scrapework.go b/lib/promscrape/scrapework.go
index adff00c2c..313734d6b 100644
--- a/lib/promscrape/scrapework.go
+++ b/lib/promscrape/scrapework.go
@@ -6,7 +6,6 @@ import (
 	"math"
 	"math/bits"
 	"strconv"
-	"strings"
 	"sync"
 	"time"
 
@@ -76,7 +75,7 @@ type ScrapeWork struct {
 	ProxyURL proxy.URL
 
 	// Optional `metric_relabel_configs`.
-	MetricRelabelConfigs []promrelabel.ParsedRelabelConfig
+	MetricRelabelConfigs *promrelabel.ParsedConfigs
 
 	// The maximum number of metrics to scrape after relabeling.
 	SampleLimit int
@@ -105,18 +104,10 @@ func (sw *ScrapeWork) key() string {
 	key := fmt.Sprintf("ScrapeURL=%s, ScrapeInterval=%s, ScrapeTimeout=%s, HonorLabels=%v, HonorTimestamps=%v, Labels=%s, "+
 		"AuthConfig=%s, MetricRelabelConfigs=%s, SampleLimit=%d, DisableCompression=%v, DisableKeepAlive=%v, StreamParse=%v, ScrapeAlignInterval=%s",
 		sw.ScrapeURL, sw.ScrapeInterval, sw.ScrapeTimeout, sw.HonorLabels, sw.HonorTimestamps, sw.LabelsString(),
-		sw.AuthConfig.String(), sw.metricRelabelConfigsString(), sw.SampleLimit, sw.DisableCompression, sw.DisableKeepAlive, sw.StreamParse, sw.ScrapeAlignInterval)
+		sw.AuthConfig.String(), sw.MetricRelabelConfigs.String(), sw.SampleLimit, sw.DisableCompression, sw.DisableKeepAlive, sw.StreamParse, sw.ScrapeAlignInterval)
 	return key
 }
 
-func (sw *ScrapeWork) metricRelabelConfigsString() string {
-	var sb strings.Builder
-	for _, prc := range sw.MetricRelabelConfigs {
-		fmt.Fprintf(&sb, "%s", prc.String())
-	}
-	return sb.String()
-}
-
 // Job returns job for the ScrapeWork
 func (sw *ScrapeWork) Job() string {
 	return promrelabel.GetLabelValueByName(sw.Labels, "job")
@@ -503,7 +494,7 @@ func (sw *scrapeWork) addRowToTimeseries(wc *writeRequestCtx, r *parser.Row, tim
 	labelsLen := len(wc.labels)
 	wc.labels = appendLabels(wc.labels, r.Metric, r.Tags, sw.Config.Labels, sw.Config.HonorLabels)
 	if needRelabel {
-		wc.labels = promrelabel.ApplyRelabelConfigs(wc.labels, labelsLen, sw.Config.MetricRelabelConfigs, true)
+		wc.labels = sw.Config.MetricRelabelConfigs.Apply(wc.labels, labelsLen, true)
 	} else {
 		wc.labels = promrelabel.FinalizeLabels(wc.labels[:labelsLen], wc.labels[labelsLen:])
 		promrelabel.SortLabels(wc.labels[labelsLen:])
diff --git a/lib/storage/index_db.go b/lib/storage/index_db.go
index e01efa14c..6ff77048f 100644
--- a/lib/storage/index_db.go
+++ b/lib/storage/index_db.go
@@ -2458,7 +2458,7 @@ func (is *indexSearch) getMetricIDsForTagFilterSlow(tf *tagFilter, filter *uint6
 		}
 		// Slow path: need tf.matchSuffix call.
 		ok, err := tf.matchSuffix(suffix)
-		loopsCount += reMatchCost
+		loopsCount += tf.matchCost
 		if err != nil {
 			return loopsCount, fmt.Errorf("error when matching %s against suffix %q: %w", tf, suffix, err)
 		}
@@ -2797,9 +2797,12 @@ func (is *indexSearch) getMetricIDsForDateAndFilters(date uint64, tfs *TagFilter
 		tf := &tfs.tfs[i]
 		loopsCount, lastQueryTimestamp := is.getLoopsCountAndTimestampForDateFilter(date, tf)
 		origLoopsCount := loopsCount
-		if currentTime > lastQueryTimestamp+60*60 {
-			// Reset loopsCount to 0 every hour for collecting updated stats for the tf.
-			loopsCount = 0
+		if currentTime > lastQueryTimestamp+3*3600 {
+			// Update stats once per 3 hours only for relatively fast tag filters.
+			// There is no need in spending CPU resources on updating stats for slow tag filters.
+			if loopsCount <= 10e6 {
+				loopsCount = 0
+			}
 		}
 		if loopsCount == 0 {
 			// Prevent from possible thundering herd issue when heavy tf is executed from multiple concurrent queries
@@ -3458,24 +3461,24 @@ func (mp *tagToMetricIDsRowParser) IsDeletedTag(dmis *uint64set.Set) bool {
 	return true
 }
 
-func mergeTagToMetricIDsRows(data []byte, items [][]byte) ([]byte, [][]byte) {
+func mergeTagToMetricIDsRows(data []byte, items []mergeset.Item) ([]byte, []mergeset.Item) {
 	data, items = mergeTagToMetricIDsRowsInternal(data, items, nsPrefixTagToMetricIDs)
 	data, items = mergeTagToMetricIDsRowsInternal(data, items, nsPrefixDateTagToMetricIDs)
 	return data, items
 }
 
-func mergeTagToMetricIDsRowsInternal(data []byte, items [][]byte, nsPrefix byte) ([]byte, [][]byte) {
+func mergeTagToMetricIDsRowsInternal(data []byte, items []mergeset.Item, nsPrefix byte) ([]byte, []mergeset.Item) {
 	// Perform quick checks whether items contain rows starting from nsPrefix
 	// based on the fact that items are sorted.
 	if len(items) <= 2 {
 		// The first and the last row must remain unchanged.
 		return data, items
 	}
-	firstItem := items[0]
+	firstItem := items[0].Bytes(data)
 	if len(firstItem) > 0 && firstItem[0] > nsPrefix {
 		return data, items
 	}
-	lastItem := items[len(items)-1]
+	lastItem := items[len(items)-1].Bytes(data)
 	if len(lastItem) > 0 && lastItem[0] < nsPrefix {
 		return data, items
 	}
@@ -3488,14 +3491,18 @@ func mergeTagToMetricIDsRowsInternal(data []byte, items [][]byte, nsPrefix byte)
 	mpPrev := &tmm.mpPrev
 	dstData := data[:0]
 	dstItems := items[:0]
-	for i, item := range items {
+	for i, it := range items {
+		item := it.Bytes(data)
 		if len(item) == 0 || item[0] != nsPrefix || i == 0 || i == len(items)-1 {
 			// Write rows not starting with nsPrefix as-is.
 			// Additionally write the first and the last row as-is in order to preserve
 			// sort order for adjancent blocks.
 			dstData, dstItems = tmm.flushPendingMetricIDs(dstData, dstItems, mpPrev)
 			dstData = append(dstData, item...)
-			dstItems = append(dstItems, dstData[len(dstData)-len(item):])
+			dstItems = append(dstItems, mergeset.Item{
+				Start: uint32(len(dstData) - len(item)),
+				End:   uint32(len(dstData)),
+			})
 			continue
 		}
 		if err := mp.Init(item, nsPrefix); err != nil {
@@ -3504,7 +3511,10 @@ func mergeTagToMetricIDsRowsInternal(data []byte, items [][]byte, nsPrefix byte)
 		if mp.MetricIDsLen() >= maxMetricIDsPerRow {
 			dstData, dstItems = tmm.flushPendingMetricIDs(dstData, dstItems, mpPrev)
 			dstData = append(dstData, item...)
-			dstItems = append(dstItems, dstData[len(dstData)-len(item):])
+			dstItems = append(dstItems, mergeset.Item{
+				Start: uint32(len(dstData) - len(item)),
+				End:   uint32(len(dstData)),
+			})
 			continue
 		}
 		if !mp.EqualPrefix(mpPrev) {
@@ -3520,7 +3530,7 @@ func mergeTagToMetricIDsRowsInternal(data []byte, items [][]byte, nsPrefix byte)
 	if len(tmm.pendingMetricIDs) > 0 {
 		logger.Panicf("BUG: tmm.pendingMetricIDs must be empty at this point; got %d items: %d", len(tmm.pendingMetricIDs), tmm.pendingMetricIDs)
 	}
-	if !checkItemsSorted(dstItems) {
+	if !checkItemsSorted(dstData, dstItems) {
 		// Items could become unsorted if initial items contain duplicate metricIDs:
 		//
 		//   item1: 1, 1, 5
@@ -3538,15 +3548,8 @@ func mergeTagToMetricIDsRowsInternal(data []byte, items [][]byte, nsPrefix byte)
 		// into the same new time series from multiple concurrent goroutines.
 		atomic.AddUint64(&indexBlocksWithMetricIDsIncorrectOrder, 1)
 		dstData = append(dstData[:0], tmm.dataCopy...)
-		dstItems = dstItems[:0]
-		// tmm.itemsCopy can point to overwritten data, so it must be updated
-		// to point to real data from tmm.dataCopy.
-		buf := dstData
-		for _, item := range tmm.itemsCopy {
-			dstItems = append(dstItems, buf[:len(item)])
-			buf = buf[len(item):]
-		}
-		if !checkItemsSorted(dstItems) {
+		dstItems = append(dstItems[:0], tmm.itemsCopy...)
+		if !checkItemsSorted(dstData, dstItems) {
 			logger.Panicf("BUG: the original items weren't sorted; items=%q", dstItems)
 		}
 	}
@@ -3558,13 +3561,14 @@ func mergeTagToMetricIDsRowsInternal(data []byte, items [][]byte, nsPrefix byte)
 var indexBlocksWithMetricIDsIncorrectOrder uint64
 var indexBlocksWithMetricIDsProcessed uint64
 
-func checkItemsSorted(items [][]byte) bool {
+func checkItemsSorted(data []byte, items []mergeset.Item) bool {
 	if len(items) == 0 {
 		return true
 	}
-	prevItem := items[0]
-	for _, currItem := range items[1:] {
-		if string(prevItem) > string(currItem) {
+	prevItem := items[0].String(data)
+	for _, it := range items[1:] {
+		currItem := it.String(data)
+		if prevItem > currItem {
 			return false
 		}
 		prevItem = currItem
@@ -3592,7 +3596,7 @@ type tagToMetricIDsRowsMerger struct {
 	mp               tagToMetricIDsRowParser
 	mpPrev           tagToMetricIDsRowParser
 
-	itemsCopy [][]byte
+	itemsCopy []mergeset.Item
 	dataCopy  []byte
 }
 
@@ -3605,7 +3609,7 @@ func (tmm *tagToMetricIDsRowsMerger) Reset() {
 	tmm.dataCopy = tmm.dataCopy[:0]
 }
 
-func (tmm *tagToMetricIDsRowsMerger) flushPendingMetricIDs(dstData []byte, dstItems [][]byte, mp *tagToMetricIDsRowParser) ([]byte, [][]byte) {
+func (tmm *tagToMetricIDsRowsMerger) flushPendingMetricIDs(dstData []byte, dstItems []mergeset.Item, mp *tagToMetricIDsRowParser) ([]byte, []mergeset.Item) {
 	if len(tmm.pendingMetricIDs) == 0 {
 		// Nothing to flush
 		return dstData, dstItems
@@ -3620,7 +3624,10 @@ func (tmm *tagToMetricIDsRowsMerger) flushPendingMetricIDs(dstData []byte, dstIt
 	for _, metricID := range tmm.pendingMetricIDs {
 		dstData = encoding.MarshalUint64(dstData, metricID)
 	}
-	dstItems = append(dstItems, dstData[dstDataLen:])
+	dstItems = append(dstItems, mergeset.Item{
+		Start: uint32(dstDataLen),
+		End:   uint32(len(dstData)),
+	})
 	tmm.pendingMetricIDs = tmm.pendingMetricIDs[:0]
 	return dstData, dstItems
 }
diff --git a/lib/storage/part.go b/lib/storage/part.go
index 52b0ec41a..378cdc869 100644
--- a/lib/storage/part.go
+++ b/lib/storage/part.go
@@ -145,21 +145,6 @@ func (idxb *indexBlock) SizeBytes() int {
 	return cap(idxb.bhs) * int(unsafe.Sizeof(blockHeader{}))
 }
 
-func getIndexBlock() *indexBlock {
-	v := indexBlockPool.Get()
-	if v == nil {
-		return &indexBlock{}
-	}
-	return v.(*indexBlock)
-}
-
-func putIndexBlock(ib *indexBlock) {
-	ib.bhs = ib.bhs[:0]
-	indexBlockPool.Put(ib)
-}
-
-var indexBlockPool sync.Pool
-
 type indexBlockCache struct {
 	// Put atomic counters to the top of struct in order to align them to 8 bytes on 32-bit architectures.
 	// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/212
@@ -198,12 +183,6 @@ func newIndexBlockCache() *indexBlockCache {
 func (ibc *indexBlockCache) MustClose(isBig bool) {
 	close(ibc.cleanerStopCh)
 	ibc.cleanerWG.Wait()
-
-	// It is safe returning ibc.m itemst to the pool, since Reset must
-	// be called only when no other goroutines access ibc entries.
-	for _, ibe := range ibc.m {
-		putIndexBlock(ibe.ib)
-	}
 	ibc.m = nil
 }
 
@@ -259,7 +238,6 @@ func (ibc *indexBlockCache) Put(k uint64, ib *indexBlock) {
 		// Remove 10% of items from the cache.
 		overflow = int(float64(len(ibc.m)) * 0.1)
 		for k := range ibc.m {
-			// Do not call putIndexBlock on ibc.m entries, since they may be used by concurrent goroutines.
 			delete(ibc.m, k)
 			overflow--
 			if overflow == 0 {
diff --git a/lib/storage/part_search.go b/lib/storage/part_search.go
index 705595b53..5acc087a4 100644
--- a/lib/storage/part_search.go
+++ b/lib/storage/part_search.go
@@ -218,7 +218,7 @@ func (ps *partSearch) readIndexBlock(mr *metaindexRow) (*indexBlock, error) {
 	if err != nil {
 		return nil, fmt.Errorf("cannot decompress index block: %w", err)
 	}
-	ib := getIndexBlock()
+	ib := &indexBlock{}
 	ib.bhs, err = unmarshalBlockHeaders(ib.bhs[:0], ps.indexBuf, int(mr.BlockHeadersCount))
 	if err != nil {
 		return nil, fmt.Errorf("cannot unmarshal index block: %w", err)
diff --git a/lib/storage/partition.go b/lib/storage/partition.go
index ff2ca08ab..3d9518c55 100644
--- a/lib/storage/partition.go
+++ b/lib/storage/partition.go
@@ -1469,12 +1469,6 @@ func appendPartsToMerge(dst, src []*partWrapper, maxPartsToMerge int, maxRows ui
 				continue
 			}
 			rowsCount := getRowsCount(a)
-			if rowsCount < 1e6 && len(a) < maxPartsToMerge {
-				// Do not merge parts with too small number of rows if the number of source parts
-				// isn't equal to maxPartsToMerge. This should reduce CPU usage and disk IO usage
-				// for small parts merge.
-				continue
-			}
 			if rowsCount > maxRows {
 				// There is no need in verifying remaining parts with higher number of rows
 				needFreeSpace = true
diff --git a/vendor/github.com/VictoriaMetrics/metrics/README.md b/vendor/github.com/VictoriaMetrics/metrics/README.md
index 4f1283abb..c76406cc2 100644
--- a/vendor/github.com/VictoriaMetrics/metrics/README.md
+++ b/vendor/github.com/VictoriaMetrics/metrics/README.md
@@ -93,7 +93,7 @@ exposed from your application.
 #### How to implement [CounterVec](https://godoc.org/github.com/prometheus/client_golang/prometheus#CounterVec) in `metrics`?
 
 Just use [GetOrCreateCounter](http://godoc.org/github.com/VictoriaMetrics/metrics#GetOrCreateCounter)
-instead of `CounterVec.With`. See [this example](https://godoc.org/github.com/VictoriaMetrics/metrics#example-Counter--Vec) for details.
+instead of `CounterVec.With`. See [this example](https://pkg.go.dev/github.com/VictoriaMetrics/metrics#example-Counter-Vec) for details.
 
 
 #### Why [Histogram](http://godoc.org/github.com/VictoriaMetrics/metrics#Histogram) buckets contain `vmrange` labels instead of `le` labels like in Prometheus histograms?
diff --git a/vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux.go b/vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux.go
index f04a50393..895ca72dd 100644
--- a/vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux.go
+++ b/vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux.go
@@ -12,8 +12,6 @@ import (
 	"time"
 )
 
-const statFilepath = "/proc/self/stat"
-
 // See https://github.com/prometheus/procfs/blob/a4ac0826abceb44c40fc71daed2b301db498b93e/proc_stat.go#L40 .
 const userHZ = 100
 
@@ -44,6 +42,7 @@ type procStat struct {
 }
 
 func writeProcessMetrics(w io.Writer) {
+	statFilepath := "/proc/self/stat"
 	data, err := ioutil.ReadFile(statFilepath)
 	if err != nil {
 		log.Printf("ERROR: cannot open %s: %s", statFilepath, err)
@@ -68,7 +67,8 @@ func writeProcessMetrics(w io.Writer) {
 	}
 
 	// It is expensive obtaining `process_open_fds` when big number of file descriptors is opened,
-	// don't do it here.
+	// so don't do it here.
+	// See writeFDMetrics instead.
 
 	utime := float64(p.Utime) / userHZ
 	stime := float64(p.Stime) / userHZ
@@ -81,6 +81,54 @@ func writeProcessMetrics(w io.Writer) {
 	fmt.Fprintf(w, "process_resident_memory_bytes %d\n", p.Rss*4096)
 	fmt.Fprintf(w, "process_start_time_seconds %d\n", startTimeSeconds)
 	fmt.Fprintf(w, "process_virtual_memory_bytes %d\n", p.Vsize)
+
+	writeIOMetrics(w)
+}
+
+func writeIOMetrics(w io.Writer) {
+	ioFilepath := "/proc/self/io"
+	data, err := ioutil.ReadFile(ioFilepath)
+	if err != nil {
+		log.Printf("ERROR: cannot open %q: %s", ioFilepath, err)
+	}
+	getInt := func(s string) int64 {
+		n := strings.IndexByte(s, ' ')
+		if n < 0 {
+			log.Printf("ERROR: cannot find whitespace in %q at %q", s, ioFilepath)
+			return 0
+		}
+		v, err := strconv.ParseInt(s[n+1:], 10, 64)
+		if err != nil {
+			log.Printf("ERROR: cannot parse %q at %q: %s", s, ioFilepath, err)
+			return 0
+		}
+		return v
+	}
+	var rchar, wchar, syscr, syscw, readBytes, writeBytes int64
+	lines := strings.Split(string(data), "\n")
+	for _, s := range lines {
+		s = strings.TrimSpace(s)
+		switch {
+		case strings.HasPrefix(s, "rchar: "):
+			rchar = getInt(s)
+		case strings.HasPrefix(s, "wchar: "):
+			wchar = getInt(s)
+		case strings.HasPrefix(s, "syscr: "):
+			syscr = getInt(s)
+		case strings.HasPrefix(s, "syscw: "):
+			syscw = getInt(s)
+		case strings.HasPrefix(s, "read_bytes: "):
+			readBytes = getInt(s)
+		case strings.HasPrefix(s, "write_bytes: "):
+			writeBytes = getInt(s)
+		}
+	}
+	fmt.Fprintf(w, "process_io_read_bytes_total %d\n", rchar)
+	fmt.Fprintf(w, "process_io_written_bytes_total %d\n", wchar)
+	fmt.Fprintf(w, "process_io_read_syscalls_total %d\n", syscr)
+	fmt.Fprintf(w, "process_io_write_syscalls_total %d\n", syscw)
+	fmt.Fprintf(w, "process_io_storage_read_bytes_total %d\n", readBytes)
+	fmt.Fprintf(w, "process_io_storage_written_bytes_total %d\n", writeBytes)
 }
 
 var startTimeSeconds = time.Now().Unix()
diff --git a/vendor/github.com/VictoriaMetrics/metricsql/rollup.go b/vendor/github.com/VictoriaMetrics/metricsql/rollup.go
index 80cfe58f9..085ee96c7 100644
--- a/vendor/github.com/VictoriaMetrics/metricsql/rollup.go
+++ b/vendor/github.com/VictoriaMetrics/metricsql/rollup.go
@@ -38,6 +38,7 @@ var rollupFuncs = map[string]bool{
 	"distinct_over_time":    true,
 	"increases_over_time":   true,
 	"decreases_over_time":   true,
+	"increase_pure":         true,
 	"integrate":             true,
 	"ideriv":                true,
 	"lifetime":              true,
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 242c38ca3..fa5b55f16 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -14,9 +14,9 @@ github.com/VictoriaMetrics/fastcache
 github.com/VictoriaMetrics/fasthttp
 github.com/VictoriaMetrics/fasthttp/fasthttputil
 github.com/VictoriaMetrics/fasthttp/stackless
-# github.com/VictoriaMetrics/metrics v1.14.0
+# github.com/VictoriaMetrics/metrics v1.15.0
 github.com/VictoriaMetrics/metrics
-# github.com/VictoriaMetrics/metricsql v0.10.1
+# github.com/VictoriaMetrics/metricsql v0.11.0
 github.com/VictoriaMetrics/metricsql
 github.com/VictoriaMetrics/metricsql/binaryop
 # github.com/VividCortex/ewma v1.1.1


Test patch:
diff --git a/app/vmselect/promql/exec_test.go b/app/vmselect/promql/exec_test.go
index 6c3cd65df..8ed198353 100644
--- a/app/vmselect/promql/exec_test.go
+++ b/app/vmselect/promql/exec_test.go
@@ -5252,6 +5252,17 @@ func TestExecSuccess(t *testing.T) {
 		resultExpected := []netstorage.Result{r}
 		f(q, resultExpected)
 	})
+	t.Run(`increase_pure(time())`, func(t *testing.T) {
+		t.Parallel()
+		q := `increase_pure(time())`
+		r := netstorage.Result{
+			MetricName: metricNameExpected,
+			Values:     []float64{200, 200, 200, 200, 200, 200},
+			Timestamps: timestampsExpected,
+		}
+		resultExpected := []netstorage.Result{r}
+		f(q, resultExpected)
+	})
 	t.Run(`increase(time())`, func(t *testing.T) {
 		t.Parallel()
 		q := `increase(time())`
diff --git a/app/vmselect/promql/rollup_test.go b/app/vmselect/promql/rollup_test.go
index 2820b5b7f..4b5eb540f 100644
--- a/app/vmselect/promql/rollup_test.go
+++ b/app/vmselect/promql/rollup_test.go
@@ -476,6 +476,7 @@ func TestRollupNewRollupFuncSuccess(t *testing.T) {
 	f("ideriv", 0)
 	f("decreases_over_time", 5)
 	f("increases_over_time", 5)
+	f("increase_pure", 398)
 	f("ascent_over_time", 142)
 	f("descent_over_time", 231)
 	f("zscore_over_time", -0.4254336383156416)
diff --git a/lib/mergeset/block_stream_reader_test.go b/lib/mergeset/block_stream_reader_test.go
index 056b05cec..c9175549d 100644
--- a/lib/mergeset/block_stream_reader_test.go
+++ b/lib/mergeset/block_stream_reader_test.go
@@ -44,8 +44,10 @@ func testBlockStreamReaderRead(ip *inmemoryPart, items []string) error {
 	bsr := newTestBlockStreamReader(ip)
 	i := 0
 	for bsr.Next() {
-		for _, item := range bsr.Block.items {
-			if string(item) != items[i] {
+		data := bsr.Block.data
+		for _, it := range bsr.Block.items {
+			item := it.String(data)
+			if item != items[i] {
 				return fmt.Errorf("unexpected item[%d]; got %q; want %q", i, item, items[i])
 			}
 			i++
diff --git a/lib/mergeset/encoding_test.go b/lib/mergeset/encoding_test.go
index 549a45d65..993c4b6ef 100644
--- a/lib/mergeset/encoding_test.go
+++ b/lib/mergeset/encoding_test.go
@@ -37,8 +37,10 @@ func TestInmemoryBlockAdd(t *testing.T) {
 		if len(ib.data) != totalLen {
 			t.Fatalf("unexpected ib.data len; got %d; want %d", len(ib.data), totalLen)
 		}
-		for j, item := range ib.items {
-			if items[j] != string(item) {
+		data := ib.data
+		for j, it := range ib.items {
+			item := it.String(data)
+			if items[j] != item {
 				t.Fatalf("unexpected item at index %d out of %d, loop %d\ngot\n%X\nwant\n%X", j, len(items), i, item, items[j])
 			}
 		}
@@ -75,8 +77,10 @@ func TestInmemoryBlockSort(t *testing.T) {
 		if len(ib.data) != totalLen {
 			t.Fatalf("unexpected ib.data len; got %d; want %d", len(ib.data), totalLen)
 		}
-		for j, item := range ib.items {
-			if items[j] != string(item) {
+		data := ib.data
+		for j, it := range ib.items {
+			item := it.String(data)
+			if items[j] != item {
 				t.Fatalf("unexpected item at index %d out of %d, loop %d\ngot\n%X\nwant\n%X", j, len(items), i, item, items[j])
 			}
 		}
@@ -122,8 +126,9 @@ func TestInmemoryBlockMarshalUnmarshal(t *testing.T) {
 		if int(itemsLen) != len(ib.items) {
 			t.Fatalf("unexpected number of items marshaled; got %d; want %d", itemsLen, len(ib.items))
 		}
-		if string(firstItem) != string(ib.items[0]) {
-			t.Fatalf("unexpected the first item\ngot\n%q\nwant\n%q", firstItem, ib.items[0])
+		firstItemExpected := ib.items[0].String(ib.data)
+		if string(firstItem) != firstItemExpected {
+			t.Fatalf("unexpected the first item\ngot\n%q\nwant\n%q", firstItem, firstItemExpected)
 		}
 		if err := checkMarshalType(mt); err != nil {
 			t.Fatalf("invalid mt: %s", err)
@@ -143,12 +148,15 @@ func TestInmemoryBlockMarshalUnmarshal(t *testing.T) {
 			t.Fatalf("unexpected ib.data len; got %d; want %d", len(ib2.data), totalLen)
 		}
 		for j := range items {
-			if len(items[j]) != len(ib2.items[j]) {
+			it2 := ib2.items[j]
+			item2 := it2.String(ib2.data)
+			if len(items[j]) != len(item2) {
 				t.Fatalf("items length mismatch at index %d out of %d, loop %d\ngot\n(len=%d) %X\nwant\n(len=%d) %X",
-					j, len(items), i, len(ib2.items[j]), ib2.items[j], len(items[j]), items[j])
+					j, len(items), i, len(item2), item2, len(items[j]), items[j])
 			}
 		}
-		for j, item := range ib2.items {
+		for j, it := range ib2.items {
+			item := it.String(ib2.data)
 			if items[j] != string(item) {
 				t.Fatalf("unexpected item at index %d out of %d, loop %d\ngot\n(len=%d) %X\nwant\n(len=%d) %X",
 					j, len(items), i, len(item), item, len(items[j]), items[j])
diff --git a/lib/mergeset/merge_test.go b/lib/mergeset/merge_test.go
index 8d34ce421..f042bae0f 100644
--- a/lib/mergeset/merge_test.go
+++ b/lib/mergeset/merge_test.go
@@ -157,10 +157,12 @@ func testCheckItems(dstIP *inmemoryPart, items []string) error {
 		if bh.itemsCount <= 0 {
 			return fmt.Errorf("unexpected empty block")
 		}
-		if string(bh.firstItem) != string(dstBsr.Block.items[0]) {
-			return fmt.Errorf("unexpected blockHeader.firstItem; got %q; want %q", bh.firstItem, dstBsr.Block.items[0])
+		item := dstBsr.Block.items[0].Bytes(dstBsr.Block.data)
+		if string(bh.firstItem) != string(item) {
+			return fmt.Errorf("unexpected blockHeader.firstItem; got %q; want %q", bh.firstItem, item)
 		}
-		for _, item := range dstBsr.Block.items {
+		for _, it := range dstBsr.Block.items {
+			item := it.Bytes(dstBsr.Block.data)
 			dstItems = append(dstItems, string(item))
 		}
 	}
diff --git a/lib/mergeset/table_search_timing_test.go b/lib/mergeset/table_search_timing_test.go
index f8d6e3515..add04e46e 100644
--- a/lib/mergeset/table_search_timing_test.go
+++ b/lib/mergeset/table_search_timing_test.go
@@ -46,7 +46,7 @@ func benchmarkTableSearch(b *testing.B, itemsCount int) {
 	b.Run("sequential-keys-exact", func(b *testing.B) {
 		benchmarkTableSearchKeys(b, tb, keys, 0)
 	})
-	b.Run("sequential-keys-without-siffux", func(b *testing.B) {
+	b.Run("sequential-keys-without-suffix", func(b *testing.B) {
 		benchmarkTableSearchKeys(b, tb, keys, 4)
 	})
 
@@ -57,7 +57,7 @@ func benchmarkTableSearch(b *testing.B, itemsCount int) {
 	b.Run("random-keys-exact", func(b *testing.B) {
 		benchmarkTableSearchKeys(b, tb, randKeys, 0)
 	})
-	b.Run("random-keys-without-siffux", func(b *testing.B) {
+	b.Run("random-keys-without-suffix", func(b *testing.B) {
 		benchmarkTableSearchKeys(b, tb, randKeys, 4)
 	})
 }
diff --git a/lib/mergeset/table_test.go b/lib/mergeset/table_test.go
index aa43fe9e1..f61d2e635 100644
--- a/lib/mergeset/table_test.go
+++ b/lib/mergeset/table_test.go
@@ -218,7 +218,7 @@ func TestTableAddItemsConcurrent(t *testing.T) {
 		atomic.AddUint64(&flushes, 1)
 	}
 	var itemsMerged uint64
-	prepareBlock := func(data []byte, items [][]byte) ([]byte, [][]byte) {
+	prepareBlock := func(data []byte, items []Item) ([]byte, []Item) {
 		atomic.AddUint64(&itemsMerged, uint64(len(items)))
 		return data, items
 	}
diff --git a/lib/promrelabel/config_test.go b/lib/promrelabel/config_test.go
index 3a1ae1477..f514d4d9a 100644
--- a/lib/promrelabel/config_test.go
+++ b/lib/promrelabel/config_test.go
@@ -7,12 +7,12 @@ import (
 
 func TestLoadRelabelConfigsSuccess(t *testing.T) {
 	path := "testdata/relabel_configs_valid.yml"
-	prcs, err := LoadRelabelConfigs(path)
+	pcs, err := LoadRelabelConfigs(path)
 	if err != nil {
 		t.Fatalf("cannot load relabel configs from %q: %s", path, err)
 	}
-	if len(prcs) != 9 {
-		t.Fatalf("unexpected number of relabel configs loaded from %q; got %d; want %d", path, len(prcs), 9)
+	if n := pcs.Len(); n != 9 {
+		t.Fatalf("unexpected number of relabel configs loaded from %q; got %d; want %d", path, n, 9)
 	}
 }
 
@@ -23,7 +23,7 @@ func TestLoadRelabelConfigsFailure(t *testing.T) {
 		if err == nil {
 			t.Fatalf("expecting non-nil error")
 		}
-		if len(rcs) != 0 {
+		if rcs.Len() != 0 {
 			t.Fatalf("unexpected non-empty rcs: %#v", rcs)
 		}
 	}
@@ -36,14 +36,14 @@ func TestLoadRelabelConfigsFailure(t *testing.T) {
 }
 
 func TestParseRelabelConfigsSuccess(t *testing.T) {
-	f := func(rcs []RelabelConfig, prcsExpected []ParsedRelabelConfig) {
+	f := func(rcs []RelabelConfig, pcsExpected *ParsedConfigs) {
 		t.Helper()
-		prcs, err := ParseRelabelConfigs(nil, rcs)
+		pcs, err := ParseRelabelConfigs(rcs)
 		if err != nil {
 			t.Fatalf("unexected error: %s", err)
 		}
-		if !reflect.DeepEqual(prcs, prcsExpected) {
-			t.Fatalf("unexpected prcs; got\n%#v\nwant\n%#v", prcs, prcsExpected)
+		if !reflect.DeepEqual(pcs, pcsExpected) {
+			t.Fatalf("unexpected pcs; got\n%#v\nwant\n%#v", pcs, pcsExpected)
 		}
 	}
 	f(nil, nil)
@@ -52,16 +52,19 @@ func TestParseRelabelConfigsSuccess(t *testing.T) {
 			SourceLabels: []string{"foo", "bar"},
 			TargetLabel:  "xxx",
 		},
-	}, []ParsedRelabelConfig{
-		{
-			SourceLabels: []string{"foo", "bar"},
-			Separator:    ";",
-			TargetLabel:  "xxx",
-			Regex:        defaultRegexForRelabelConfig,
-			Replacement:  "$1",
-			Action:       "replace",
+	}, &ParsedConfigs{
+		prcs: []*parsedRelabelConfig{
+			{
+				SourceLabels: []string{"foo", "bar"},
+				Separator:    ";",
+				TargetLabel:  "xxx",
+				Regex:        defaultRegexForRelabelConfig,
+				Replacement:  "$1",
+				Action:       "replace",
 
-			hasCaptureGroupInReplacement: true,
+				regexOriginal:                defaultOriginalRegexForRelabelConfig,
+				hasCaptureGroupInReplacement: true,
+			},
 		},
 	})
 }
@@ -69,12 +72,12 @@ func TestParseRelabelConfigsSuccess(t *testing.T) {
 func TestParseRelabelConfigsFailure(t *testing.T) {
 	f := func(rcs []RelabelConfig) {
 		t.Helper()
-		prcs, err := ParseRelabelConfigs(nil, rcs)
+		pcs, err := ParseRelabelConfigs(rcs)
 		if err == nil {
 			t.Fatalf("expecting non-nil error")
 		}
-		if len(prcs) > 0 {
-			t.Fatalf("unexpected non-empty prcs: %#v", prcs)
+		if pcs.Len() > 0 {
+			t.Fatalf("unexpected non-empty pcs: %#v", pcs)
 		}
 	}
 	t.Run("invalid-regex", func(t *testing.T) {
diff --git a/lib/promrelabel/relabel_test.go b/lib/promrelabel/relabel_test.go
index a57e8bee6..cc06887e7 100644
--- a/lib/promrelabel/relabel_test.go
+++ b/lib/promrelabel/relabel_test.go
@@ -2,24 +2,27 @@ package promrelabel
 
 import (
 	"reflect"
-	"regexp"
 	"testing"
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
 )
 
 func TestApplyRelabelConfigs(t *testing.T) {
-	f := func(prcs []ParsedRelabelConfig, labels []prompbmarshal.Label, isFinalize bool, resultExpected []prompbmarshal.Label) {
+	f := func(config string, labels []prompbmarshal.Label, isFinalize bool, resultExpected []prompbmarshal.Label) {
 		t.Helper()
-		result := ApplyRelabelConfigs(labels, 0, prcs, isFinalize)
+		pcs, err := ParseRelabelConfigsData([]byte(config))
+		if err != nil {
+			t.Fatalf("cannot parse %q: %s", config, err)
+		}
+		result := pcs.Apply(labels, 0, isFinalize)
 		if !reflect.DeepEqual(result, resultExpected) {
 			t.Fatalf("unexpected result; got\n%v\nwant\n%v", result, resultExpected)
 		}
 	}
 	t.Run("empty_relabel_configs", func(t *testing.T) {
-		f(nil, nil, false, nil)
-		f(nil, nil, true, nil)
-		f(nil, []prompbmarshal.Label{
+		f("", nil, false, nil)
+		f("", nil, true, nil)
+		f("", []prompbmarshal.Label{
 			{
 				Name:  "foo",
 				Value: "bar",
@@ -30,7 +33,7 @@ func TestApplyRelabelConfigs(t *testing.T) {
 				Value: "bar",
 			},
 		})
-		f(nil, []prompbmarshal.Label{
+		f("", []prompbmarshal.Label{
 			{
 				Name:  "foo",
 				Value: "bar",
@@ -55,35 +58,20 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		})
 	})
 	t.Run("replace-miss", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
-			{
-				Action:                       "replace",
-				TargetLabel:                  "bar",
-				Regex:                        defaultRegexForRelabelConfig,
-				Replacement:                  "$1",
-				hasCaptureGroupInReplacement: true,
-			},
-		}, nil, false, []prompbmarshal.Label{})
-		f([]ParsedRelabelConfig{
-			{
-				Action:                       "replace",
-				SourceLabels:                 []string{"foo"},
-				TargetLabel:                  "bar",
-				Regex:                        defaultRegexForRelabelConfig,
-				Replacement:                  "$1",
-				hasCaptureGroupInReplacement: true,
-			},
-		}, nil, false, []prompbmarshal.Label{})
-		f([]ParsedRelabelConfig{
-			{
-				Action:                       "replace",
-				SourceLabels:                 []string{"foo"},
-				TargetLabel:                  "bar",
-				Regex:                        defaultRegexForRelabelConfig,
-				Replacement:                  "$1",
-				hasCaptureGroupInReplacement: true,
-			},
-		}, []prompbmarshal.Label{
+		f(`
+- action: replace
+  target_label: bar
+`, nil, false, []prompbmarshal.Label{})
+		f(`
+- action: replace
+  source_labels: ["foo"]
+  target_label: bar
+`, nil, false, []prompbmarshal.Label{})
+		f(`
+- action: replace
+  source_labels: ["foo"]
+  target_label: "bar"
+`, []prompbmarshal.Label{
 			{
 				Name:  "xxx",
 				Value: "yyy",
@@ -94,16 +82,12 @@ func TestApplyRelabelConfigs(t *testing.T) {
 				Value: "yyy",
 			},
 		})
-		f([]ParsedRelabelConfig{
-			{
-				Action:                       "replace",
-				SourceLabels:                 []string{"foo"},
-				TargetLabel:                  "bar",
-				Regex:                        regexp.MustCompile(".+"),
-				Replacement:                  "$1",
-				hasCaptureGroupInReplacement: true,
-			},
-		}, []prompbmarshal.Label{
+		f(`
+- action: replace
+  source_labels: ["foo"]
+  target_label: "bar"
+  regex: ".+"
+`, []prompbmarshal.Label{
 			{
 				Name:  "xxx",
 				Value: "yyy",
@@ -116,17 +100,12 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		})
 	})
 	t.Run("replace-hit", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
-			{
-				Action:                       "replace",
-				SourceLabels:                 []string{"xxx", "foo"},
-				Separator:                    ";",
-				TargetLabel:                  "bar",
-				Regex:                        defaultRegexForRelabelConfig,
-				Replacement:                  "a-$1-b",
-				hasCaptureGroupInReplacement: true,
-			},
-		}, []prompbmarshal.Label{
+		f(`
+- action: replace
+  source_labels: ["xxx", "foo"]
+  target_label: "bar"
+  replacement: "a-$1-b"
+`, []prompbmarshal.Label{
 			{
 				Name:  "xxx",
 				Value: "yyy",
@@ -143,18 +122,12 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		})
 	})
 	t.Run("replace-hit-target-label-with-capture-group", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
-			{
-				Action:                       "replace",
-				SourceLabels:                 []string{"xxx", "foo"},
-				Separator:                    ";",
-				TargetLabel:                  "bar-$1",
-				Regex:                        defaultRegexForRelabelConfig,
-				Replacement:                  "a-$1-b",
-				hasCaptureGroupInTargetLabel: true,
-				hasCaptureGroupInReplacement: true,
-			},
-		}, []prompbmarshal.Label{
+		f(`
+- action: replace
+  source_labels: ["xxx", "foo"]
+  target_label: "bar-$1"
+  replacement: "a-$1-b"
+`, []prompbmarshal.Label{
 			{
 				Name:  "xxx",
 				Value: "yyy",
@@ -171,35 +144,21 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		})
 	})
 	t.Run("replace_all-miss", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
-			{
-				Action:                       "replace_all",
-				TargetLabel:                  "bar",
-				Regex:                        defaultRegexForRelabelConfig,
-				Replacement:                  "$1",
-				hasCaptureGroupInReplacement: true,
-			},
-		}, nil, false, []prompbmarshal.Label{})
-		f([]ParsedRelabelConfig{
-			{
-				Action:                       "replace_all",
-				SourceLabels:                 []string{"foo"},
-				TargetLabel:                  "bar",
-				Regex:                        defaultRegexForRelabelConfig,
-				Replacement:                  "$1",
-				hasCaptureGroupInReplacement: true,
-			},
-		}, nil, false, []prompbmarshal.Label{})
-		f([]ParsedRelabelConfig{
-			{
-				Action:                       "replace_all",
-				SourceLabels:                 []string{"foo"},
-				TargetLabel:                  "bar",
-				Regex:                        defaultRegexForRelabelConfig,
-				Replacement:                  "$1",
-				hasCaptureGroupInReplacement: true,
-			},
-		}, []prompbmarshal.Label{
+		f(`
+- action: replace_all
+  source_labels: [foo]
+  target_label: "bar"
+`, nil, false, []prompbmarshal.Label{})
+		f(`
+- action: replace_all
+  source_labels: ["foo"]
+  target_label: "bar"
+`, nil, false, []prompbmarshal.Label{})
+		f(`
+- action: replace_all
+  source_labels: ["foo"]
+  target_label: "bar"
+`, []prompbmarshal.Label{
 			{
 				Name:  "xxx",
 				Value: "yyy",
@@ -210,16 +169,12 @@ func TestApplyRelabelConfigs(t *testing.T) {
 				Value: "yyy",
 			},
 		})
-		f([]ParsedRelabelConfig{
-			{
-				Action:                       "replace_all",
-				SourceLabels:                 []string{"foo"},
-				TargetLabel:                  "bar",
-				Regex:                        regexp.MustCompile(".+"),
-				Replacement:                  "$1",
-				hasCaptureGroupInReplacement: true,
-			},
-		}, []prompbmarshal.Label{
+		f(`
+- action: replace_all
+  source_labels: ["foo"]
+  target_label: "bar"
+  regex: ".+"
+`, []prompbmarshal.Label{
 			{
 				Name:  "xxx",
 				Value: "yyy",
@@ -232,17 +187,32 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		})
 	})
 	t.Run("replace_all-hit", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
+		f(`
+- action: replace_all
+  source_labels: ["xxx"]
+  target_label: "xxx"
+  regex: "-"
+  replacement: "."
+`, []prompbmarshal.Label{
 			{
-				Action:                       "replace_all",
-				SourceLabels:                 []string{"xxx", "foo"},
-				Separator:                    ";",
-				TargetLabel:                  "xxx",
-				Regex:                        regexp.MustCompile("(;)"),
-				Replacement:                  "-$1-",
-				hasCaptureGroupInReplacement: true,
+				Name:  "xxx",
+				Value: "a-b-c",
 			},
-		}, []prompbmarshal.Label{
+		}, false, []prompbmarshal.Label{
+			{
+				Name:  "xxx",
+				Value: "a.b.c",
+			},
+		})
+	})
+	t.Run("replace_all-regex-hit", func(t *testing.T) {
+		f(`
+- action: replace_all
+  source_labels: ["xxx", "foo"]
+  target_label: "xxx"
+  regex: "(;)"
+  replacement: "-$1-"
+`, []prompbmarshal.Label{
 			{
 				Name:  "xxx",
 				Value: "y;y",
@@ -255,23 +225,16 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		})
 	})
 	t.Run("replace-add-multi-labels", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
-			{
-				Action:                       "replace",
-				SourceLabels:                 []string{"xxx"},
-				TargetLabel:                  "bar",
-				Regex:                        defaultRegexForRelabelConfig,
-				Replacement:                  "a-$1",
-				hasCaptureGroupInReplacement: true,
-			},
-			{
-				Action:       "replace",
-				SourceLabels: []string{"bar"},
-				TargetLabel:  "zar",
-				Regex:        defaultRegexForRelabelConfig,
-				Replacement:  "b-$1",
-			},
-		}, []prompbmarshal.Label{
+		f(`
+- action: replace
+  source_labels: ["xxx"]
+  target_label: "bar"
+  replacement: "a-$1"
+- action: replace
+  source_labels: ["bar"]
+  target_label: "zar"
+  replacement: "b-$1"
+`, []prompbmarshal.Label{
 			{
 				Name:  "xxx",
 				Value: "yyy",
@@ -300,16 +263,12 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		})
 	})
 	t.Run("replace-self", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
-			{
-				Action:                       "replace",
-				SourceLabels:                 []string{"foo"},
-				TargetLabel:                  "foo",
-				Regex:                        defaultRegexForRelabelConfig,
-				Replacement:                  "a-$1",
-				hasCaptureGroupInReplacement: true,
-			},
-		}, []prompbmarshal.Label{
+		f(`
+- action: replace
+  source_labels: ["foo"]
+  target_label: "foo"
+  replacement: "a-$1"
+`, []prompbmarshal.Label{
 			{
 				Name:  "foo",
 				Value: "aaxx",
@@ -322,14 +281,11 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		})
 	})
 	t.Run("replace-missing-source", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
-			{
-				Action:      "replace",
-				TargetLabel: "foo",
-				Regex:       defaultRegexForRelabelConfig,
-				Replacement: "foobar",
-			},
-		}, []prompbmarshal.Label{}, true, []prompbmarshal.Label{
+		f(`
+- action: replace
+  target_label: foo
+  replacement: "foobar"
+`, []prompbmarshal.Label{}, true, []prompbmarshal.Label{
 			{
 				Name:  "foo",
 				Value: "foobar",
@@ -337,18 +293,14 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		})
 	})
 	t.Run("keep_if_equal-miss", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
-			{
-				Action:       "keep_if_equal",
-				SourceLabels: []string{"foo", "bar"},
-			},
-		}, nil, true, nil)
-		f([]ParsedRelabelConfig{
-			{
-				Action:       "keep_if_equal",
-				SourceLabels: []string{"xxx", "bar"},
-			},
-		}, []prompbmarshal.Label{
+		f(`
+- action: keep_if_equal
+  source_labels: ["foo", "bar"]
+`, nil, true, nil)
+		f(`
+- action: keep_if_equal
+  source_labels: ["xxx", "bar"]
+`, []prompbmarshal.Label{
 			{
 				Name:  "xxx",
 				Value: "yyy",
@@ -356,12 +308,10 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		}, true, []prompbmarshal.Label{})
 	})
 	t.Run("keep_if_equal-hit", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
-			{
-				Action:       "keep_if_equal",
-				SourceLabels: []string{"xxx", "bar"},
-			},
-		}, []prompbmarshal.Label{
+		f(`
+- action: keep_if_equal
+  source_labels: ["xxx", "bar"]
+`, []prompbmarshal.Label{
 			{
 				Name:  "xxx",
 				Value: "yyy",
@@ -382,18 +332,14 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		})
 	})
 	t.Run("drop_if_equal-miss", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
-			{
-				Action:       "drop_if_equal",
-				SourceLabels: []string{"foo", "bar"},
-			},
-		}, nil, true, nil)
-		f([]ParsedRelabelConfig{
-			{
-				Action:       "drop_if_equal",
-				SourceLabels: []string{"xxx", "bar"},
-			},
-		}, []prompbmarshal.Label{
+		f(`
+- action: drop_if_equal
+  source_labels: ["foo", "bar"]
+`, nil, true, nil)
+		f(`
+- action: drop_if_equal
+  source_labels: ["xxx", "bar"]
+`, []prompbmarshal.Label{
 			{
 				Name:  "xxx",
 				Value: "yyy",
@@ -406,12 +352,10 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		})
 	})
 	t.Run("drop_if_equal-hit", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
-			{
-				Action:       "drop_if_equal",
-				SourceLabels: []string{"xxx", "bar"},
-			},
-		}, []prompbmarshal.Label{
+		f(`
+- action: drop_if_equal
+  source_labels: [xxx, bar]
+`, []prompbmarshal.Label{
 			{
 				Name:  "xxx",
 				Value: "yyy",
@@ -423,20 +367,16 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		}, true, []prompbmarshal.Label{})
 	})
 	t.Run("keep-miss", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
-			{
-				Action:       "keep",
-				SourceLabels: []string{"foo"},
-				Regex:        regexp.MustCompile(".+"),
-			},
-		}, nil, true, nil)
-		f([]ParsedRelabelConfig{
-			{
-				Action:       "keep",
-				SourceLabels: []string{"foo"},
-				Regex:        regexp.MustCompile(".+"),
-			},
-		}, []prompbmarshal.Label{
+		f(`
+- action: keep
+  source_labels: [foo]
+  regex: ".+"
+`, nil, true, nil)
+		f(`
+- action: keep
+  source_labels: [foo]
+  regex: ".+"
+`, []prompbmarshal.Label{
 			{
 				Name:  "xxx",
 				Value: "yyy",
@@ -444,13 +384,11 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		}, true, []prompbmarshal.Label{})
 	})
 	t.Run("keep-hit", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
-			{
-				Action:       "keep",
-				SourceLabels: []string{"foo"},
-				Regex:        regexp.MustCompile(".+"),
-			},
-		}, []prompbmarshal.Label{
+		f(`
+- action: keep
+  source_labels: [foo]
+  regex: "yyy"
+`, []prompbmarshal.Label{
 			{
 				Name:  "foo",
 				Value: "yyy",
@@ -462,21 +400,34 @@ func TestApplyRelabelConfigs(t *testing.T) {
 			},
 		})
 	})
-	t.Run("drop-miss", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
+	t.Run("keep-hit-regexp", func(t *testing.T) {
+		f(`
+- action: keep
+  source_labels: ["foo"]
+  regex: ".+"
+`, []prompbmarshal.Label{
 			{
-				Action:       "drop",
-				SourceLabels: []string{"foo"},
-				Regex:        regexp.MustCompile(".+"),
+				Name:  "foo",
+				Value: "yyy",
 			},
-		}, nil, false, nil)
-		f([]ParsedRelabelConfig{
+		}, false, []prompbmarshal.Label{
 			{
-				Action:       "drop",
-				SourceLabels: []string{"foo"},
-				Regex:        regexp.MustCompile(".+"),
+				Name:  "foo",
+				Value: "yyy",
 			},
-		}, []prompbmarshal.Label{
+		})
+	})
+	t.Run("drop-miss", func(t *testing.T) {
+		f(`
+- action: drop
+  source_labels: [foo]
+  regex: ".+"
+`, nil, false, nil)
+		f(`
+- action: drop
+  source_labels: [foo]
+  regex: ".+"
+`, []prompbmarshal.Label{
 			{
 				Name:  "xxx",
 				Value: "yyy",
@@ -489,13 +440,23 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		})
 	})
 	t.Run("drop-hit", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
+		f(`
+- action: drop
+  source_labels: [foo]
+  regex: yyy
+`, []prompbmarshal.Label{
 			{
-				Action:       "drop",
-				SourceLabels: []string{"foo"},
-				Regex:        regexp.MustCompile(".+"),
+				Name:  "foo",
+				Value: "yyy",
 			},
-		}, []prompbmarshal.Label{
+		}, true, []prompbmarshal.Label{})
+	})
+	t.Run("drop-hit-regexp", func(t *testing.T) {
+		f(`
+- action: drop
+  source_labels: [foo]
+  regex: ".+"
+`, []prompbmarshal.Label{
 			{
 				Name:  "foo",
 				Value: "yyy",
@@ -503,14 +464,12 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		}, true, []prompbmarshal.Label{})
 	})
 	t.Run("hashmod-miss", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
-			{
-				Action:       "hashmod",
-				SourceLabels: []string{"foo"},
-				TargetLabel:  "aaa",
-				Modulus:      123,
-			},
-		}, []prompbmarshal.Label{
+		f(`
+- action: hashmod
+  source_labels: [foo]
+  target_label: aaa
+  modulus: 123
+`, []prompbmarshal.Label{
 			{
 				Name:  "xxx",
 				Value: "yyy",
@@ -527,14 +486,12 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		})
 	})
 	t.Run("hashmod-hit", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
-			{
-				Action:       "hashmod",
-				SourceLabels: []string{"foo"},
-				TargetLabel:  "aaa",
-				Modulus:      123,
-			},
-		}, []prompbmarshal.Label{
+		f(`
+- action: hashmod
+  source_labels: [foo]
+  target_label: aaa
+  modulus: 123
+`, []prompbmarshal.Label{
 			{
 				Name:  "foo",
 				Value: "yyy",
@@ -550,14 +507,25 @@ func TestApplyRelabelConfigs(t *testing.T) {
 			},
 		})
 	})
-	t.Run("labelmap", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
+	t.Run("labelmap-copy-label", func(t *testing.T) {
+		f(`
+- action: labelmap
+  regex: "foo"
+  replacement: "bar"
+`, []prompbmarshal.Label{
 			{
-				Action:      "labelmap",
-				Regex:       regexp.MustCompile("foo(.+)"),
-				Replacement: "$1-x",
+				Name:  "foo",
+				Value: "yyy",
+			},
+			{
+				Name:  "foobar",
+				Value: "aaa",
+			},
+		}, true, []prompbmarshal.Label{
+			{
+				Name:  "bar",
+				Value: "yyy",
 			},
-		}, []prompbmarshal.Label{
 			{
 				Name:  "foo",
 				Value: "yyy",
@@ -566,9 +534,52 @@ func TestApplyRelabelConfigs(t *testing.T) {
 				Name:  "foobar",
 				Value: "aaa",
 			},
+		})
+	})
+	t.Run("labelmap-remove-prefix-dot-star", func(t *testing.T) {
+		f(`
+- action: labelmap
+  regex: "foo(.*)"
+`, []prompbmarshal.Label{
+			{
+				Name:  "xoo",
+				Value: "yyy",
+			},
+			{
+				Name:  "foobar",
+				Value: "aaa",
+			},
 		}, true, []prompbmarshal.Label{
 			{
-				Name:  "bar-x",
+				Name:  "bar",
+				Value: "aaa",
+			},
+			{
+				Name:  "foobar",
+				Value: "aaa",
+			},
+			{
+				Name:  "xoo",
+				Value: "yyy",
+			},
+		})
+	})
+	t.Run("labelmap-remove-prefix-dot-plus", func(t *testing.T) {
+		f(`
+- action: labelmap
+  regex: "foo(.+)"
+`, []prompbmarshal.Label{
+			{
+				Name:  "foo",
+				Value: "yyy",
+			},
+			{
+				Name:  "foobar",
+				Value: "aaa",
+			},
+		}, true, []prompbmarshal.Label{
+			{
+				Name:  "bar",
 				Value: "aaa",
 			},
 			{
@@ -581,14 +592,41 @@ func TestApplyRelabelConfigs(t *testing.T) {
 			},
 		})
 	})
-	t.Run("labelmap_all", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
+	t.Run("labelmap-regex", func(t *testing.T) {
+		f(`
+- action: labelmap
+  regex: "foo(.+)"
+  replacement: "$1-x"
+`, []prompbmarshal.Label{
 			{
-				Action:      "labelmap_all",
-				Regex:       regexp.MustCompile(`\.`),
-				Replacement: "-",
+				Name:  "foo",
+				Value: "yyy",
 			},
-		}, []prompbmarshal.Label{
+			{
+				Name:  "foobar",
+				Value: "aaa",
+			},
+		}, true, []prompbmarshal.Label{
+			{
+				Name:  "bar-x",
+				Value: "aaa",
+			},
+			{
+				Name:  "foo",
+				Value: "yyy",
+			},
+			{
+				Name:  "foobar",
+				Value: "aaa",
+			},
+		})
+	})
+	t.Run("labelmap_all", func(t *testing.T) {
+		f(`
+- action: labelmap_all
+  regex: "\\."
+  replacement: "-"
+`, []prompbmarshal.Label{
 			{
 				Name:  "foo.bar.baz",
 				Value: "yyy",
@@ -608,13 +646,36 @@ func TestApplyRelabelConfigs(t *testing.T) {
 			},
 		})
 	})
-	t.Run("labeldrop", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
+	t.Run("labelmap_all-regexp", func(t *testing.T) {
+		f(`
+- action: labelmap_all
+  regex: "ba(.)"
+  replacement: "${1}ss"
+`, []prompbmarshal.Label{
+			{
+				Name:  "foo.bar.baz",
+				Value: "yyy",
+			},
+			{
+				Name:  "foozar",
+				Value: "aaa",
+			},
+		}, true, []prompbmarshal.Label{
 			{
-				Action: "labeldrop",
-				Regex:  regexp.MustCompile("dropme.*"),
+				Name:  "foo.rss.zss",
+				Value: "yyy",
 			},
-		}, []prompbmarshal.Label{
+			{
+				Name:  "foozar",
+				Value: "aaa",
+			},
+		})
+	})
+	t.Run("labeldrop", func(t *testing.T) {
+		f(`
+- action: labeldrop
+  regex: dropme
+`, []prompbmarshal.Label{
 			{
 				Name:  "aaa",
 				Value: "bbb",
@@ -625,12 +686,52 @@ func TestApplyRelabelConfigs(t *testing.T) {
 				Value: "bbb",
 			},
 		})
-		f([]ParsedRelabelConfig{
+		f(`
+- action: labeldrop
+  regex: dropme
+`, []prompbmarshal.Label{
+			{
+				Name:  "xxx",
+				Value: "yyy",
+			},
 			{
-				Action: "labeldrop",
-				Regex:  regexp.MustCompile("dropme.*"),
+				Name:  "dropme",
+				Value: "aaa",
 			},
-		}, []prompbmarshal.Label{
+			{
+				Name:  "foo",
+				Value: "bar",
+			},
+		}, false, []prompbmarshal.Label{
+			{
+				Name:  "foo",
+				Value: "bar",
+			},
+			{
+				Name:  "xxx",
+				Value: "yyy",
+			},
+		})
+	})
+	t.Run("labeldrop-regexp", func(t *testing.T) {
+		f(`
+- action: labeldrop
+  regex: "dropme.*"
+`, []prompbmarshal.Label{
+			{
+				Name:  "aaa",
+				Value: "bbb",
+			},
+		}, true, []prompbmarshal.Label{
+			{
+				Name:  "aaa",
+				Value: "bbb",
+			},
+		})
+		f(`
+- action: labeldrop
+  regex: "dropme.*"
+`, []prompbmarshal.Label{
 			{
 				Name:  "xxx",
 				Value: "yyy",
@@ -655,28 +756,62 @@ func TestApplyRelabelConfigs(t *testing.T) {
 		})
 	})
 	t.Run("labelkeep", func(t *testing.T) {
-		f([]ParsedRelabelConfig{
+		f(`
+- action: labelkeep
+  regex: "keepme"
+`, []prompbmarshal.Label{
+			{
+				Name:  "keepme",
+				Value: "aaa",
+			},
+		}, true, []prompbmarshal.Label{
 			{
-				Action: "labelkeep",
-				Regex:  regexp.MustCompile("keepme.*"),
+				Name:  "keepme",
+				Value: "aaa",
 			},
-		}, []prompbmarshal.Label{
+		})
+		f(`
+- action: labelkeep
+  regex: keepme
+`, []prompbmarshal.Label{
 			{
 				Name:  "keepme",
 				Value: "aaa",
 			},
-		}, true, []prompbmarshal.Label{
+			{
+				Name:  "aaaa",
+				Value: "awef",
+			},
+			{
+				Name:  "keepme-aaa",
+				Value: "234",
+			},
+		}, false, []prompbmarshal.Label{
 			{
 				Name:  "keepme",
 				Value: "aaa",
 			},
 		})
-		f([]ParsedRelabelConfig{
+	})
+	t.Run("labelkeep-regexp", func(t *testing.T) {
+		f(`
+- action: labelkeep
+  regex: "keepme.*"
+`, []prompbmarshal.Label{
 			{
-				Action: "labelkeep",
-				Regex:  regexp.MustCompile("keepme.*"),
+				Name:  "keepme",
+				Value: "aaa",
 			},
-		}, []prompbmarshal.Label{
+		}, true, []prompbmarshal.Label{
+			{
+				Name:  "keepme",
+				Value: "aaa",
+			},
+		})
+		f(`
+- action: labelkeep
+  regex: "keepme.*"
+`, []prompbmarshal.Label{
 			{
 				Name:  "keepme",
 				Value: "aaa",
diff --git a/lib/promrelabel/relabel_timing_test.go b/lib/promrelabel/relabel_timing_test.go
index c16a3a113..4904bb0ef 100644
--- a/lib/promrelabel/relabel_timing_test.go
+++ b/lib/promrelabel/relabel_timing_test.go
@@ -2,7 +2,6 @@ package promrelabel
 
 import (
 	"fmt"
-	"regexp"
 	"testing"
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
@@ -10,15 +9,11 @@ import (
 
 func BenchmarkApplyRelabelConfigs(b *testing.B) {
 	b.Run("replace-label-copy", func(b *testing.B) {
-		prcs := []ParsedRelabelConfig{
-			{
-				Action:       "replace",
-				SourceLabels: []string{"id"},
-				TargetLabel:  "__name__",
-				Regex:        defaultRegexForRelabelConfig,
-				Replacement:  "$1",
-			},
-		}
+		pcs := mustParseRelabelConfigs(`
+- action: replace
+  source_labels: [id]
+  target_label: __name__
+`)
 		labelsOrig := []prompbmarshal.Label{
 			{
 				Name:  "__name__",
@@ -35,7 +30,7 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 			var labels []prompbmarshal.Label
 			for pb.Next() {
 				labels = append(labels[:0], labelsOrig...)
-				labels = ApplyRelabelConfigs(labels, 0, prcs, true)
+				labels = pcs.Apply(labels, 0, true)
 				if len(labels) != len(labelsOrig) {
 					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), len(labelsOrig), labels))
 				}
@@ -55,14 +50,11 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 		})
 	})
 	b.Run("replace-set-label", func(b *testing.B) {
-		prcs := []ParsedRelabelConfig{
-			{
-				Action:      "replace",
-				TargetLabel: "__name__",
-				Regex:       defaultRegexForRelabelConfig,
-				Replacement: "foobar",
-			},
-		}
+		pcs := mustParseRelabelConfigs(`
+- action: replace
+  target_label: __name__
+  replacement: foobar
+`)
 		labelsOrig := []prompbmarshal.Label{
 			{
 				Name:  "__name__",
@@ -79,7 +71,7 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 			var labels []prompbmarshal.Label
 			for pb.Next() {
 				labels = append(labels[:0], labelsOrig...)
-				labels = ApplyRelabelConfigs(labels, 0, prcs, true)
+				labels = pcs.Apply(labels, 0, true)
 				if len(labels) != len(labelsOrig) {
 					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), len(labelsOrig), labels))
 				}
@@ -99,14 +91,11 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 		})
 	})
 	b.Run("replace-add-label", func(b *testing.B) {
-		prcs := []ParsedRelabelConfig{
-			{
-				Action:      "replace",
-				TargetLabel: "aaa",
-				Regex:       defaultRegexForRelabelConfig,
-				Replacement: "foobar",
-			},
-		}
+		pcs := mustParseRelabelConfigs(`
+- action: replace
+  target_label: aaa
+  replacement: foobar
+`)
 		labelsOrig := []prompbmarshal.Label{
 			{
 				Name:  "__name__",
@@ -119,7 +108,7 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 			var labels []prompbmarshal.Label
 			for pb.Next() {
 				labels = append(labels[:0], labelsOrig...)
-				labels = ApplyRelabelConfigs(labels, 0, prcs, true)
+				labels = pcs.Apply(labels, 0, true)
 				if len(labels) != 2 {
 					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), 2, labels))
 				}
@@ -139,15 +128,12 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 		})
 	})
 	b.Run("replace-mismatch", func(b *testing.B) {
-		prcs := []ParsedRelabelConfig{
-			{
-				Action:       "replace",
-				SourceLabels: []string{"non-existing-label"},
-				TargetLabel:  "id",
-				Regex:        regexp.MustCompile("(foobar)-.*"),
-				Replacement:  "$1",
-			},
-		}
+		pcs := mustParseRelabelConfigs(`
+- action: replace
+  source_labels: ["non-existing-label"]
+  target_label: id
+  regex: "(foobar)-.*"
+`)
 		labelsOrig := []prompbmarshal.Label{
 			{
 				Name:  "__name__",
@@ -164,7 +150,7 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 			var labels []prompbmarshal.Label
 			for pb.Next() {
 				labels = append(labels[:0], labelsOrig...)
-				labels = ApplyRelabelConfigs(labels, 0, prcs, true)
+				labels = pcs.Apply(labels, 0, true)
 				if len(labels) != len(labelsOrig) {
 					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), len(labelsOrig), labels))
 				}
@@ -183,16 +169,13 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 			}
 		})
 	})
-	b.Run("replace-match", func(b *testing.B) {
-		prcs := []ParsedRelabelConfig{
-			{
-				Action:       "replace",
-				SourceLabels: []string{"id"},
-				TargetLabel:  "id",
-				Regex:        regexp.MustCompile("(foobar)-.*"),
-				Replacement:  "$1",
-			},
-		}
+	b.Run("replace-match-regex", func(b *testing.B) {
+		pcs := mustParseRelabelConfigs(`
+- action: replace
+  source_labels: [id]
+  target_label: id
+  regex: "(foobar)-.*"
+`)
 		labelsOrig := []prompbmarshal.Label{
 			{
 				Name:  "__name__",
@@ -209,7 +192,7 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 			var labels []prompbmarshal.Label
 			for pb.Next() {
 				labels = append(labels[:0], labelsOrig...)
-				labels = ApplyRelabelConfigs(labels, 0, prcs, true)
+				labels = pcs.Apply(labels, 0, true)
 				if len(labels) != len(labelsOrig) {
 					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), len(labelsOrig), labels))
 				}
@@ -229,13 +212,11 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 		})
 	})
 	b.Run("drop-mismatch", func(b *testing.B) {
-		prcs := []ParsedRelabelConfig{
-			{
-				Action:       "drop",
-				SourceLabels: []string{"non-existing-label"},
-				Regex:        regexp.MustCompile("(foobar)-.*"),
-			},
-		}
+		pcs := mustParseRelabelConfigs(`
+- action: drop
+  source_labels: ["non-existing-label"]
+  regex: "(foobar)-.*"
+`)
 		labelsOrig := []prompbmarshal.Label{
 			{
 				Name:  "__name__",
@@ -252,7 +233,7 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 			var labels []prompbmarshal.Label
 			for pb.Next() {
 				labels = append(labels[:0], labelsOrig...)
-				labels = ApplyRelabelConfigs(labels, 0, prcs, true)
+				labels = pcs.Apply(labels, 0, true)
 				if len(labels) != len(labelsOrig) {
 					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), len(labelsOrig), labels))
 				}
@@ -272,13 +253,40 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 		})
 	})
 	b.Run("drop-match", func(b *testing.B) {
-		prcs := []ParsedRelabelConfig{
+		pcs := mustParseRelabelConfigs(`
+- action: drop
+  source_labels: [id]
+  regex: yes
+`)
+		labelsOrig := []prompbmarshal.Label{
+			{
+				Name:  "__name__",
+				Value: "metric",
+			},
 			{
-				Action:       "drop",
-				SourceLabels: []string{"id"},
-				Regex:        regexp.MustCompile("(foobar)-.*"),
+				Name:  "id",
+				Value: "yes",
 			},
 		}
+		b.ReportAllocs()
+		b.SetBytes(1)
+		b.RunParallel(func(pb *testing.PB) {
+			var labels []prompbmarshal.Label
+			for pb.Next() {
+				labels = append(labels[:0], labelsOrig...)
+				labels = pcs.Apply(labels, 0, true)
+				if len(labels) != 0 {
+					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), 0, labels))
+				}
+			}
+		})
+	})
+	b.Run("drop-match-regexp", func(b *testing.B) {
+		pcs := mustParseRelabelConfigs(`
+- action: drop
+  source_labels: [id]
+  regex: "(foobar)-.*"
+`)
 		labelsOrig := []prompbmarshal.Label{
 			{
 				Name:  "__name__",
@@ -295,7 +303,7 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 			var labels []prompbmarshal.Label
 			for pb.Next() {
 				labels = append(labels[:0], labelsOrig...)
-				labels = ApplyRelabelConfigs(labels, 0, prcs, true)
+				labels = pcs.Apply(labels, 0, true)
 				if len(labels) != 0 {
 					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), 0, labels))
 				}
@@ -303,13 +311,11 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 		})
 	})
 	b.Run("keep-mismatch", func(b *testing.B) {
-		prcs := []ParsedRelabelConfig{
-			{
-				Action:       "keep",
-				SourceLabels: []string{"non-existing-label"},
-				Regex:        regexp.MustCompile("(foobar)-.*"),
-			},
-		}
+		pcs := mustParseRelabelConfigs(`
+- action: keep
+  source_labels: ["non-existing-label"]
+  regex: "(foobar)-.*"
+`)
 		labelsOrig := []prompbmarshal.Label{
 			{
 				Name:  "__name__",
@@ -326,7 +332,7 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 			var labels []prompbmarshal.Label
 			for pb.Next() {
 				labels = append(labels[:0], labelsOrig...)
-				labels = ApplyRelabelConfigs(labels, 0, prcs, true)
+				labels = pcs.Apply(labels, 0, true)
 				if len(labels) != 0 {
 					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), 0, labels))
 				}
@@ -334,13 +340,52 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 		})
 	})
 	b.Run("keep-match", func(b *testing.B) {
-		prcs := []ParsedRelabelConfig{
+		pcs := mustParseRelabelConfigs(`
+- action: keep
+  source_labels: [id]
+  regex: yes
+`)
+		labelsOrig := []prompbmarshal.Label{
+			{
+				Name:  "__name__",
+				Value: "metric",
+			},
 			{
-				Action:       "keep",
-				SourceLabels: []string{"id"},
-				Regex:        regexp.MustCompile("(foobar)-.*"),
+				Name:  "id",
+				Value: "yes",
 			},
 		}
+		b.ReportAllocs()
+		b.SetBytes(1)
+		b.RunParallel(func(pb *testing.PB) {
+			var labels []prompbmarshal.Label
+			for pb.Next() {
+				labels = append(labels[:0], labelsOrig...)
+				labels = pcs.Apply(labels, 0, true)
+				if len(labels) != len(labelsOrig) {
+					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), len(labelsOrig), labels))
+				}
+				if labels[0].Name != "__name__" {
+					panic(fmt.Errorf("unexpected label name; got %q; want %q", labels[0].Name, "__name__"))
+				}
+				if labels[0].Value != "metric" {
+					panic(fmt.Errorf("unexpected label value; got %q; want %q", labels[0].Value, "metric"))
+				}
+				if labels[1].Name != "id" {
+					panic(fmt.Errorf("unexpected label name; got %q; want %q", labels[1].Name, "id"))
+				}
+				if labels[1].Value != "yes" {
+					panic(fmt.Errorf("unexpected label value; got %q; want %q", labels[1].Value, "yes"))
+				}
+			}
+		})
+	})
+	b.Run("keep-match-regexp", func(b *testing.B) {
+		pcs := mustParseRelabelConfigs(`
+- action: keep
+  source_labels: [id]
+  regex: "(foobar)-.*"
+`)
 		labelsOrig := []prompbmarshal.Label{
 			{
 				Name:  "__name__",
@@ -357,7 +402,7 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 			var labels []prompbmarshal.Label
 			for pb.Next() {
 				labels = append(labels[:0], labelsOrig...)
-				labels = ApplyRelabelConfigs(labels, 0, prcs, true)
+				labels = pcs.Apply(labels, 0, true)
 				if len(labels) != len(labelsOrig) {
 					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), len(labelsOrig), labels))
 				}
@@ -377,12 +422,10 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 		})
 	})
 	b.Run("labeldrop-mismatch", func(b *testing.B) {
-		prcs := []ParsedRelabelConfig{
-			{
-				Action: "labeldrop",
-				Regex:  regexp.MustCompile("non-existing-label"),
-			},
-		}
+		pcs := mustParseRelabelConfigs(`
+- action: labeldrop
+  regex: "non-existing-label"
+`)
 		labelsOrig := []prompbmarshal.Label{
 			{
 				Name:  "__name__",
@@ -399,7 +442,7 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 			var labels []prompbmarshal.Label
 			for pb.Next() {
 				labels = append(labels[:0], labelsOrig...)
-				labels = ApplyRelabelConfigs(labels, 0, prcs, true)
+				labels = pcs.Apply(labels, 0, true)
 				if len(labels) != len(labelsOrig) {
 					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), len(labelsOrig), labels))
 				}
@@ -419,12 +462,44 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 		})
 	})
 	b.Run("labeldrop-match", func(b *testing.B) {
-		prcs := []ParsedRelabelConfig{
+		pcs := mustParseRelabelConfigs(`
+- action: labeldrop
+  regex: id
+`)
+		labelsOrig := []prompbmarshal.Label{
 			{
-				Action: "labeldrop",
-				Regex:  regexp.MustCompile("id"),
+				Name:  "__name__",
+				Value: "metric",
+			},
+			{
+				Name:  "id",
+				Value: "foobar-random-string-here",
 			},
 		}
+		b.ReportAllocs()
+		b.SetBytes(1)
+		b.RunParallel(func(pb *testing.PB) {
+			var labels []prompbmarshal.Label
+			for pb.Next() {
+				labels = append(labels[:0], labelsOrig...)
+				labels = pcs.Apply(labels, 0, true)
+				if len(labels) != 1 {
+					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), 1, labels))
+				}
+				if labels[0].Name != "__name__" {
+					panic(fmt.Errorf("unexpected label name; got %q; want %q", labels[0].Name, "__name__"))
+				}
+				if labels[0].Value != "metric" {
+					panic(fmt.Errorf("unexpected label value; got %q; want %q", labels[0].Value, "metric"))
+				}
+			}
+		})
+	})
+	b.Run("labeldrop-match-regexp", func(b *testing.B) {
+		pcs := mustParseRelabelConfigs(`
+- action: labeldrop
+  regex: "id.*"
+`)
 		labelsOrig := []prompbmarshal.Label{
 			{
 				Name:  "__name__",
@@ -441,7 +516,7 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 			var labels []prompbmarshal.Label
 			for pb.Next() {
 				labels = append(labels[:0], labelsOrig...)
-				labels = ApplyRelabelConfigs(labels, 0, prcs, true)
+				labels = pcs.Apply(labels, 0, true)
 				if len(labels) != 1 {
 					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), 1, labels))
 				}
@@ -455,12 +530,10 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 		})
 	})
 	b.Run("labelkeep-mismatch", func(b *testing.B) {
-		prcs := []ParsedRelabelConfig{
-			{
-				Action: "labelkeep",
-				Regex:  regexp.MustCompile("non-existing-label"),
-			},
-		}
+		pcs := mustParseRelabelConfigs(`
+- action: labelkeep
+  regex: "non-existing-label"
+`)
 		labelsOrig := []prompbmarshal.Label{
 			{
 				Name:  "__name__",
@@ -477,7 +550,7 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 			var labels []prompbmarshal.Label
 			for pb.Next() {
 				labels = append(labels[:0], labelsOrig...)
-				labels = ApplyRelabelConfigs(labels, 0, prcs, true)
+				labels = pcs.Apply(labels, 0, true)
 				if len(labels) != 0 {
 					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), 0, labels))
 				}
@@ -485,12 +558,44 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 		})
 	})
 	b.Run("labelkeep-match", func(b *testing.B) {
-		prcs := []ParsedRelabelConfig{
+		pcs := mustParseRelabelConfigs(`
+- action: labelkeep
+  regex: id
+`)
+		labelsOrig := []prompbmarshal.Label{
+			{
+				Name:  "__name__",
+				Value: "metric",
+			},
 			{
-				Action: "labelkeep",
-				Regex:  regexp.MustCompile("id"),
+				Name:  "id",
+				Value: "foobar-random-string-here",
 			},
 		}
+		b.ReportAllocs()
+		b.SetBytes(1)
+		b.RunParallel(func(pb *testing.PB) {
+			var labels []prompbmarshal.Label
+			for pb.Next() {
+				labels = append(labels[:0], labelsOrig...)
+				labels = pcs.Apply(labels, 0, true)
+				if len(labels) != 1 {
+					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), 1, labels))
+				}
+				if labels[0].Name != "id" {
+					panic(fmt.Errorf("unexpected label name; got %q; want %q", labels[0].Name, "id"))
+				}
+				if labels[0].Value != "foobar-random-string-here" {
+					panic(fmt.Errorf("unexpected label value; got %q; want %q", labels[0].Value, "foobar-random-string-here"))
+				}
+			}
+		})
+	})
+	b.Run("labelkeep-match-regexp", func(b *testing.B) {
+		pcs := mustParseRelabelConfigs(`
+- action: labelkeep
+  regex: "id.*"
+`)
 		labelsOrig := []prompbmarshal.Label{
 			{
 				Name:  "__name__",
@@ -507,7 +612,7 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 			var labels []prompbmarshal.Label
 			for pb.Next() {
 				labels = append(labels[:0], labelsOrig...)
-				labels = ApplyRelabelConfigs(labels, 0, prcs, true)
+				labels = pcs.Apply(labels, 0, true)
 				if len(labels) != 1 {
 					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), 1, labels))
 				}
@@ -520,15 +625,115 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 			}
 		})
 	})
-	b.Run("hashmod", func(b *testing.B) {
-		prcs := []ParsedRelabelConfig{
+	b.Run("labelmap-mismatch", func(b *testing.B) {
+		pcs := mustParseRelabelConfigs(`
+- action: labelmap
+  regex: "a(.*)"
+`)
+		labelsOrig := []prompbmarshal.Label{
+			{
+				Name:  "foo",
+				Value: "bar",
+			},
+		}
+		b.ReportAllocs()
+		b.SetBytes(1)
+		b.RunParallel(func(pb *testing.PB) {
+			var labels []prompbmarshal.Label
+			for pb.Next() {
+				labels = append(labels[:0], labelsOrig...)
+				labels = pcs.Apply(labels, 0, true)
+				if len(labels) != 1 {
+					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), 3, labels))
+				}
+				if labels[0].Name != "foo" {
+					panic(fmt.Errorf("unexpected label name; got %q; want %q", labels[0].Name, "foo"))
+				}
+				if labels[0].Value != "bar" {
+					panic(fmt.Errorf("unexpected label value; got %q; want %q", labels[0].Value, "bar"))
+				}
+			}
+		})
+	})
+	b.Run("labelmap-match-remove-prefix", func(b *testing.B) {
+		pcs := mustParseRelabelConfigs(`
+- action: labelmap
+  regex: "a(.*)"
+`)
+		labelsOrig := []prompbmarshal.Label{
 			{
-				Action:       "hashmod",
-				SourceLabels: []string{"id"},
-				TargetLabel:  "id",
-				Modulus:      23,
+				Name:  "aabc",
+				Value: "foobar-random-string-here",
 			},
 		}
+		b.ReportAllocs()
+		b.SetBytes(1)
+		b.RunParallel(func(pb *testing.PB) {
+			var labels []prompbmarshal.Label
+			for pb.Next() {
+				labels = append(labels[:0], labelsOrig...)
+				labels = pcs.Apply(labels, 0, true)
+				if len(labels) != 2 {
+					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), 3, labels))
+				}
+				if labels[0].Name != "aabc" {
+					panic(fmt.Errorf("unexpected label name; got %q; want %q", labels[0].Name, "aabc"))
+				}
+				if labels[0].Value != "foobar-random-string-here" {
+					panic(fmt.Errorf("unexpected label value; got %q; want %q", labels[0].Value, "foobar-random-string-here"))
+				}
+				if labels[1].Name != "abc" {
+					panic(fmt.Errorf("unexpected label name; got %q; want %q", labels[1].Name, "abc"))
+				}
+				if labels[1].Value != "foobar-random-string-here" {
+					panic(fmt.Errorf("unexpected label value; got %q; want %q", labels[1].Value, "foobar-random-string-here"))
+				}
+			}
+		})
+	})
+	b.Run("labelmap-match-regexp", func(b *testing.B) {
+		pcs := mustParseRelabelConfigs(`
+- action: labelmap
+  regex: "(.*)bc"
+`)
+		labelsOrig := []prompbmarshal.Label{
+			{
+				Name:  "aabc",
+				Value: "foobar-random-string-here",
+			},
+		}
+		b.ReportAllocs()
+		b.SetBytes(1)
+		b.RunParallel(func(pb *testing.PB) {
+			var labels []prompbmarshal.Label
+			for pb.Next() {
+				labels = append(labels[:0], labelsOrig...)
+				labels = pcs.Apply(labels, 0, true)
+				if len(labels) != 2 {
+					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), 3, labels))
+				}
+				if labels[0].Name != "aa" {
+					panic(fmt.Errorf("unexpected label name; got %q; want %q", labels[0].Name, "aa"))
+				}
+				if labels[0].Value != "foobar-random-string-here" {
+					panic(fmt.Errorf("unexpected label value; got %q; want %q", labels[0].Value, "foobar-random-string-here"))
+				}
+				if labels[1].Name != "aabc" {
+					panic(fmt.Errorf("unexpected label name; got %q; want %q", labels[1].Name, "aabc"))
+				}
+				if labels[1].Value != "foobar-random-string-here" {
+					panic(fmt.Errorf("unexpected label value; got %q; want %q", labels[1].Value, "foobar-random-string-here"))
+				}
+			}
+		})
+	})
+	b.Run("hashmod", func(b *testing.B) {
+		pcs := mustParseRelabelConfigs(`
+- action: hashmod
+  source_labels: [id]
+  target_label: id
+  modulus: 23
+`)
 		labelsOrig := []prompbmarshal.Label{
 			{
 				Name:  "__name__",
@@ -545,7 +750,7 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 			var labels []prompbmarshal.Label
 			for pb.Next() {
 				labels = append(labels[:0], labelsOrig...)
-				labels = ApplyRelabelConfigs(labels, 0, prcs, true)
+				labels = pcs.Apply(labels, 0, true)
 				if len(labels) != len(labelsOrig) {
 					panic(fmt.Errorf("unexpected number of labels; got %d; want %d; labels:\n%#v", len(labels), len(labelsOrig), labels))
 				}
@@ -565,3 +770,11 @@ func BenchmarkApplyRelabelConfigs(b *testing.B) {
 		})
 	})
 }
+
+func mustParseRelabelConfigs(config string) *ParsedConfigs {
+	pcs, err := ParseRelabelConfigsData([]byte(config))
+	if err != nil {
+		panic(fmt.Errorf("unexpected error: %w", err))
+	}
+	return pcs
+}
diff --git a/lib/promscrape/config_test.go b/lib/promscrape/config_test.go
index a1dbc5f57..f2273ccc4 100644
--- a/lib/promscrape/config_test.go
+++ b/lib/promscrape/config_test.go
@@ -4,13 +4,11 @@ import (
 	"crypto/tls"
 	"fmt"
 	"reflect"
-	"regexp"
 	"testing"
 	"time"
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
-	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
 )
 
 func TestLoadStaticConfigs(t *testing.T) {
@@ -1034,13 +1032,6 @@ scrape_configs:
 		},
 	})
 
-	prcs, err := promrelabel.ParseRelabelConfigs(nil, []promrelabel.RelabelConfig{{
-		SourceLabels: []string{"foo"},
-		TargetLabel:  "abc",
-	}})
-	if err != nil {
-		t.Fatalf("unexpected error when parsing relabel configs: %s", err)
-	}
 	f(`
 scrape_configs:
 - job_name: foo
@@ -1076,9 +1067,12 @@ scrape_configs:
 					Value: "foo",
 				},
 			},
-			AuthConfig:           &promauth.Config{},
-			MetricRelabelConfigs: prcs,
-			jobNameOriginal:      "foo",
+			AuthConfig: &promauth.Config{},
+			MetricRelabelConfigs: mustParseRelabelConfigs(`
+- source_labels: [foo]
+  target_label: abc
+`),
+			jobNameOriginal: "foo",
 		},
 	})
 	f(`
@@ -1374,8 +1368,6 @@ scrape_configs:
 	})
 }
 
-var defaultRegexForRelabelConfig = regexp.MustCompile("^(.*)$")
-
 func equalStaticConfigForScrapeWorks(a, b []*ScrapeWork) bool {
 	if len(a) != len(b) {
 		return false
diff --git a/lib/promscrape/scrapework_test.go b/lib/promscrape/scrapework_test.go
index 3e9fb9577..74b6f67e8 100644
--- a/lib/promscrape/scrapework_test.go
+++ b/lib/promscrape/scrapework_test.go
@@ -2,7 +2,6 @@ package promscrape
 
 import (
 	"fmt"
-	"regexp"
 	"strings"
 	"testing"
 
@@ -102,7 +101,8 @@ func TestScrapeWorkScrapeInternalSuccess(t *testing.T) {
 		sw.PushData = func(wr *prompbmarshal.WriteRequest) {
 			pushDataCalls++
 			if len(wr.Timeseries) > len(timeseriesExpected) {
-				pushDataErr = fmt.Errorf("too many time series obtained; got %d; want %d", len(wr.Timeseries), len(timeseriesExpected))
+				pushDataErr = fmt.Errorf("too many time series obtained; got %d; want %d\ngot\n%+v\nwant\n%+v",
+					len(wr.Timeseries), len(timeseriesExpected), wr.Timeseries, timeseriesExpected)
 				return
 			}
 			tsExpected := timeseriesExpected[:len(wr.Timeseries)]
@@ -271,20 +271,14 @@ func TestScrapeWorkScrapeInternalSuccess(t *testing.T) {
 				Value: "foo.com",
 			},
 		},
-		MetricRelabelConfigs: []promrelabel.ParsedRelabelConfig{
-			{
-				SourceLabels: []string{"__address__", "job"},
-				Separator:    "/",
-				TargetLabel:  "instance",
-				Regex:        defaultRegexForRelabelConfig,
-				Replacement:  "$1",
-				Action:       "replace",
-			},
-			{
-				Action: "labeldrop",
-				Regex:  regexp.MustCompile("^c$"),
-			},
-		},
+		MetricRelabelConfigs: mustParseRelabelConfigs(`
+- action: replace
+  source_labels: ["__address__", "job"]
+  separator: "/"
+  target_label: "instance"
+- action: labeldrop
+  regex: c
+`),
 	}, `
 		foo{bar="baz",job="xx",instance="foo.com/xx"} 34.44 123
 		bar{a="b",job="xx",instance="foo.com/xx"} -3e4 123
@@ -311,18 +305,15 @@ func TestScrapeWorkScrapeInternalSuccess(t *testing.T) {
 				Value: "foo.com",
 			},
 		},
-		MetricRelabelConfigs: []promrelabel.ParsedRelabelConfig{
-			{
-				Action:       "drop",
-				SourceLabels: []string{"a", "c"},
-				Regex:        regexp.MustCompile("^bd$"),
-			},
-			{
-				Action:       "drop",
-				SourceLabels: []string{"__name__"},
-				Regex:        regexp.MustCompile("^(dropme|up)$"),
-			},
-		},
+		MetricRelabelConfigs: mustParseRelabelConfigs(`
+- action: drop
+  separator: ""
+  source_labels: [a, c]
+  regex: "^bd$"
+- action: drop
+  source_labels: [__name__]
+  regex: "dropme|up"
+`),
 	}, `
 		foo{bar="baz",job="xx",instance="foo.com"} 34.44 123
 		up{job="xx",instance="foo.com"} 1 123
@@ -440,3 +431,11 @@ func timeseriesToString(ts *prompbmarshal.TimeSeries) string {
 	fmt.Fprintf(&sb, "%g %d", s.Value, s.Timestamp)
 	return sb.String()
 }
+
+func mustParseRelabelConfigs(config string) *promrelabel.ParsedConfigs {
+	pcs, err := promrelabel.ParseRelabelConfigsData([]byte(config))
+	if err != nil {
+		panic(fmt.Errorf("cannot parse %q: %w", config, err))
+	}
+	return pcs
+}
diff --git a/lib/storage/block_header_test.go b/lib/storage/block_header_test.go
index cffcfcb7e..aaa597914 100644
--- a/lib/storage/block_header_test.go
+++ b/lib/storage/block_header_test.go
@@ -76,6 +76,6 @@ func testBlockHeaderMarshalUnmarshal(t *testing.T, bh *blockHeader) {
 		t.Fatalf("unexpected tail after unmarshaling bh=%+v; got\n%x; want\n%x", bh, tail, suffix)
 	}
 	if !reflect.DeepEqual(bh, &bh2) {
-		t.Fatalf("unexpected bh unmarshaled after adding siffux; got\n%+v; want\n%+v", &bh2, bh)
+		t.Fatalf("unexpected bh unmarshaled after adding suffix; got\n%+v; want\n%+v", &bh2, bh)
 	}
 }
diff --git a/lib/storage/index_db_test.go b/lib/storage/index_db_test.go
index c77d99e02..25fbe858c 100644
--- a/lib/storage/index_db_test.go
+++ b/lib/storage/index_db_test.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/mergeset"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/uint64set"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/workingsetcache"
 )
@@ -36,33 +37,38 @@ func TestMergeTagToMetricIDsRows(t *testing.T) {
 	f := func(items []string, expectedItems []string) {
 		t.Helper()
 		var data []byte
-		var itemsB [][]byte
+		var itemsB []mergeset.Item
 		for _, item := range items {
 			data = append(data, item...)
-			itemsB = append(itemsB, data[len(data)-len(item):])
+			itemsB = append(itemsB, mergeset.Item{
+				Start: uint32(len(data) - len(item)),
+				End:   uint32(len(data)),
+			})
 		}
-		if !checkItemsSorted(itemsB) {
+		if !checkItemsSorted(data, itemsB) {
 			t.Fatalf("source items aren't sorted; items:\n%q", itemsB)
 		}
 		resultData, resultItemsB := mergeTagToMetricIDsRows(data, itemsB)
 		if len(resultItemsB) != len(expectedItems) {
 			t.Fatalf("unexpected len(resultItemsB); got %d; want %d", len(resultItemsB), len(expectedItems))
 		}
-		if !checkItemsSorted(resultItemsB) {
+		if !checkItemsSorted(resultData, resultItemsB) {
 			t.Fatalf("result items aren't sorted; items:\n%q", resultItemsB)
 		}
-		for i, item := range resultItemsB {
-			if !bytes.HasPrefix(resultData, item) {
-				t.Fatalf("unexpected prefix for resultData #%d;\ngot\n%X\nwant\n%X", i, resultData, item)
+		buf := resultData
+		for i, it := range resultItemsB {
+			item := it.Bytes(resultData)
+			if !bytes.HasPrefix(buf, item) {
+				t.Fatalf("unexpected prefix for resultData #%d;\ngot\n%X\nwant\n%X", i, buf, item)
 			}
-			resultData = resultData[len(item):]
+			buf = buf[len(item):]
 		}
-		if len(resultData) != 0 {
-			t.Fatalf("unexpected tail left in resultData: %X", resultData)
+		if len(buf) != 0 {
+			t.Fatalf("unexpected tail left in resultData: %X", buf)
 		}
 		var resultItems []string
-		for _, item := range resultItemsB {
-			resultItems = append(resultItems, string(item))
+		for _, it := range resultItemsB {
+			resultItems = append(resultItems, string(it.Bytes(resultData)))
 		}
 		if !reflect.DeepEqual(expectedItems, resultItems) {
 			t.Fatalf("unexpected items;\ngot\n%X\nwant\n%X", resultItems, expectedItems)
diff --git a/lib/storage/partition_test.go b/lib/storage/partition_test.go
index 0deebbadd..60c3a84d2 100644
--- a/lib/storage/partition_test.go
+++ b/lib/storage/partition_test.go
@@ -26,9 +26,11 @@ func TestAppendPartsToMerge(t *testing.T) {
 	testAppendPartsToMerge(t, 2, []uint64{4, 2, 4}, []uint64{4, 4})
 	testAppendPartsToMerge(t, 2, []uint64{1, 3, 7, 2}, nil)
 	testAppendPartsToMerge(t, 3, []uint64{1, 3, 7, 2}, []uint64{1, 2, 3})
-	testAppendPartsToMerge(t, 4, []uint64{1, 3, 7, 2}, nil)
+	testAppendPartsToMerge(t, 4, []uint64{1, 3, 7, 2}, []uint64{1, 2, 3})
+	testAppendPartsToMerge(t, 5, []uint64{1, 3, 7, 2}, nil)
 	testAppendPartsToMerge(t, 4, []uint64{1e6, 3e6, 7e6, 2e6}, []uint64{1e6, 2e6, 3e6})
-	testAppendPartsToMerge(t, 4, []uint64{2, 3, 7, 2}, []uint64{2, 2, 3, 7})
+	testAppendPartsToMerge(t, 4, []uint64{2, 3, 7, 2}, []uint64{2, 2, 3})
+	testAppendPartsToMerge(t, 5, []uint64{2, 3, 7, 2}, nil)
 	testAppendPartsToMerge(t, 3, []uint64{11, 1, 10, 100, 10}, []uint64{10, 10, 11})
 }