Repository: VictoriaMetrics/VictoriaMetrics
Pull request #5331 (2023-11-15 20:22:21)

Automatic update operator docs from VictoriaMetrics/operator@bc8b02f


Patch:
diff --git a/README.md b/README.md
index 3af49d93d..e0f78d728 100644
--- a/README.md
+++ b/README.md
@@ -87,6 +87,7 @@ VictoriaMetrics has the following prominent features:
   * [Arbitrary CSV data](#how-to-import-csv-data).
   * [Native binary format](#how-to-import-data-in-native-format).
   * [DataDog agent or DogStatsD](#how-to-send-data-from-datadog-agent).
+  * [NewRelic infrastructure agent](#how-to-send-data-from-newrelic-agent).
   * [OpenTelemetry metrics format](#sending-data-via-opentelemetry).
 * It supports powerful [stream aggregation](https://docs.victoriametrics.com/stream-aggregation.html), which can be used as a [statsd](https://github.com/statsd/statsd) alternative.
 * It supports metrics [relabeling](#relabeling).
diff --git a/app/vmagent/README.md b/app/vmagent/README.md
index 3a1984fa2..452fd8482 100644
--- a/app/vmagent/README.md
+++ b/app/vmagent/README.md
@@ -95,6 +95,7 @@ additionally to pull-based Prometheus-compatible targets' scraping:
 * InfluxDB line protocol via `http://<vmagent>:8429/write`. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-send-data-from-influxdb-compatible-agents-such-as-telegraf).
 * Graphite plaintext protocol if `-graphiteListenAddr` command-line flag is set. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-send-data-from-graphite-compatible-agents-such-as-statsd).
 * OpenTelemetry http API. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#sending-data-via-opentelemetry).
+* NewRelic API. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-send-data-from-newrelic-agent).
 * OpenTSDB telnet and http protocols if `-opentsdbListenAddr` command-line flag is set. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-send-data-from-opentsdb-compatible-agents).
 * Prometheus remote write protocol via `http://<vmagent>:8429/api/v1/write`.
 * JSON lines import protocol via `http://<vmagent>:8429/api/v1/import`. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-import-data-in-json-line-format).
@@ -176,8 +177,8 @@ to the same second-level `vmagent` instance, so they are aggregated properly.
 If `-remoteWrite.shardByURL` command-line flag is set, then all the metric labels are used for even sharding
 among remote storage systems specified in `-remoteWrite.url`. Sometimes it may be needed to use only a particular
 set of labels for sharding. For example, it may be needed to route all the metrics with the same `instance` label
-to the same `-remoteWrite.url`. In this case you can specify comma-separated list of these labels in the `-remoteWrite.shardByURLLabels`
-command-line flag. For example, `-remoteWrite.shardByURLLabels=instance,__name__` would shard metrics with the same name and `instance`
+to the same `-remoteWrite.url`. In this case you can specify comma-separated list of these labels in the `-remoteWrite.shardByURL.labels`
+command-line flag. For example, `-remoteWrite.shardByURL.labels=instance,__name__` would shard metrics with the same name and `instance`
 label to the same `-remoteWrite.url`.
 
 See also [how to scrape big number of targets](#scraping-big-number-of-targets).
diff --git a/app/vmalert/README.md b/app/vmalert/README.md
index b47b5fc23..0de72579a 100644
--- a/app/vmalert/README.md
+++ b/app/vmalert/README.md
@@ -542,10 +542,20 @@ Alertmanagers.
 
 To avoid recording rules results and alerts state duplication in VictoriaMetrics server
 don't forget to configure [deduplication](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#deduplication).
-The recommended value for `-dedup.minScrapeInterval` must be multiple of vmalert's `-evaluationInterval`.
+Multiple equally configured vmalerts should evaluate rules at the same timestamps, so it is recommended 
+to set `-dedup.minScrapeInterval` as equal to vmalert's `-evaluationInterval`.
+
+If you have multiple different `interval` params for distinct rule groups, then set `-dedup.minScrapeInterval` to
+the biggest `interval` value, or value which will be a multiple for all `interval` values. For example, if you have
+two groups with `interval: 10s` and `interval: 15s`, then set `-dedup.minScrapeInterval=30s`. This would consistently
+keep only a single data point on 30s time interval for all rules. However, try to avoid having inconsistent `interval`
+values.
+
+It is not recommended having `-dedup.minScrapeInterval` smaller than `-evaluationInterval`, as it may produce 
+results with inconsistent intervals between data points.
 
 Alertmanager will automatically deduplicate alerts with identical labels, so ensure that
-all `vmalert`s are having the same config.
+all `vmalert`s are having identical config.
 
 Don't forget to configure [cluster mode](https://prometheus.io/docs/alerting/latest/alertmanager/)
 for Alertmanagers for better reliability. List all Alertmanager URLs in vmalert `-notifier.url`
diff --git a/app/vmbackup/README.md b/app/vmbackup/README.md
index 6fbb5effd..5eb2b824b 100644
--- a/app/vmbackup/README.md
+++ b/app/vmbackup/README.md
@@ -265,7 +265,7 @@ You have to add a custom url endpoint via flag:
       -customS3Endpoint=https://s3-fips.us-gov-west-1.amazonaws.com
     ```
 
-### Permanent deletion of objects in S3 and compatible storages
+### Permanent deletion of objects in S3-compatible storages
 
 `vmbackup` and [vmbackupmanager](https://docs.victoriametrics.com/vmbackupmanager.html) use standard delete operation
 for S3-compatible object storage when pefrorming [incremental backups](#incremental-backups).
diff --git a/app/vmbackupmanager/README.md b/app/vmbackupmanager/README.md
index b64921363..32d5914cc 100644
--- a/app/vmbackupmanager/README.md
+++ b/app/vmbackupmanager/README.md
@@ -126,7 +126,7 @@ Backup retention policy is controlled by:
 
 > *Note*: 0 value in every keepLast flag results into deletion of ALL backups for particular type (hourly, daily, weekly and monthly)
 
-> *Note*: retention policy does not enforce removing previous versions of objects in object storages such if versioning is enabled. See [these docs](https://docs.victoriametrics.com/vmbackup.html#permanent-deletion-of-objects-in-s3-and-compatible-storages) for more details.
+> *Note*: retention policy does not enforce removing previous versions of objects in object storages such if versioning is enabled. See [these docs](https://docs.victoriametrics.com/vmbackup.html#permanent-deletion-of-objects-in-s3-compatible-storages) for more details.
 
 Let’s assume we have a backup manager collecting daily backups for the past 10 days.
 
diff --git a/app/vmselect/promql/eval.go b/app/vmselect/promql/eval.go
index 3d4d105f2..6fb9f5ce2 100644
--- a/app/vmselect/promql/eval.go
+++ b/app/vmselect/promql/eval.go
@@ -875,7 +875,7 @@ func evalRollupFuncWithoutAt(qt *querytracer.Tracer, ec *EvalConfig, funcName st
 		}
 	}
 	if funcName == "absent_over_time" {
-		rvs = aggregateAbsentOverTime(ec, re.Expr, rvs)
+		rvs = aggregateAbsentOverTime(ecNew, re.Expr, rvs)
 	}
 	if offset != 0 && len(rvs) > 0 {
 		// Make a copy of timestamps, since they may be used in other values.
@@ -1596,8 +1596,23 @@ func evalRollupFuncWithMetricExpr(qt *querytracer.Tracer, ec *EvalConfig, funcNa
 		}
 		return rvs, nil
 	}
+	pointsPerSeries := 1 + (ec.End-ec.Start)/ec.Step
+	evalWithConfig := func(ec *EvalConfig) ([]*timeseries, error) {
+		tss, err := evalRollupFuncNoCache(qt, ec, funcName, rf, expr, me, iafc, window, pointsPerSeries)
+		if err != nil {
+			err = &UserReadableError{
+				Err: err,
+			}
+			return nil, err
+		}
+		return tss, nil
+	}
+	if !ec.mayCache() {
+		qt.Printf("do not fetch series from cache, since it is disabled in the current context")
+		return evalWithConfig(ec)
+	}
 
-	// Search for partial results in cache.
+	// Search for cached results.
 	tssCached, start := rollupResultCacheV.GetSeries(qt, ec, expr, window)
 	ec.QueryStats.addSeriesFetched(len(tssCached))
 	if start > ec.End {
@@ -1613,23 +1628,28 @@ func evalRollupFuncWithMetricExpr(qt *querytracer.Tracer, ec *EvalConfig, funcNa
 		rollupResultCacheMiss.Inc()
 	}
 
-	ecCopy := copyEvalConfig(ec)
-	ecCopy.Start = start
-	pointsPerSeries := 1 + (ec.End-ec.Start)/ec.Step
-	tss, err := evalRollupFuncNoCache(qt, ecCopy, funcName, rf, expr, me, iafc, window, pointsPerSeries)
-	if err != nil {
-		err = &UserReadableError{
-			Err: err,
-		}
-		return nil, err
+	// Fetch missing results, which aren't cached yet.
+	ecNew := ec
+	if start != ec.Start {
+		ecNew = copyEvalConfig(ec)
+		ecNew.Start = start
 	}
-	rvs, err := mergeTimeseries(qt, tssCached, tss, start, ec)
+	tss, err := evalWithConfig(ecNew)
 	if err != nil {
-		return nil, fmt.Errorf("cannot merge series: %w", err)
+		return nil, err
 	}
-	if tss != nil {
-		rollupResultCacheV.PutSeries(qt, ec, expr, window, tss)
+
+	// Merge cached results with the fetched additional results.
+	rvs, ok := mergeSeries(qt, tssCached, tss, start, ec)
+	if !ok {
+		// Cannot merge series - fall back to non-cached querying.
+		qt.Printf("fall back to non-caching querying")
+		rvs, err = evalWithConfig(ec)
+		if err != nil {
+			return nil, err
+		}
 	}
+	rollupResultCacheV.PutSeries(qt, ec, expr, window, rvs)
 	return rvs, nil
 }
 
diff --git a/app/vmselect/promql/rollup_result_cache.go b/app/vmselect/promql/rollup_result_cache.go
index f0b98381e..45d02be8d 100644
--- a/app/vmselect/promql/rollup_result_cache.go
+++ b/app/vmselect/promql/rollup_result_cache.go
@@ -273,10 +273,6 @@ func (rrc *rollupResultCache) GetSeries(qt *querytracer.Tracer, ec *EvalConfig,
 		qt = qt.NewChild("rollup cache get series: query=%s, timeRange=%s, window=%d, step=%d", query, ec.timeRangeString(), window, ec.Step)
 		defer qt.Done()
 	}
-	if !ec.mayCache() {
-		qt.Printf("do not fetch series from cache, since it is disabled in the current context")
-		return nil, ec.Start
-	}
 
 	// Obtain tss from the cache.
 	bb := bbPool.Get()
@@ -358,15 +354,27 @@ func (rrc *rollupResultCache) PutSeries(qt *querytracer.Tracer, ec *EvalConfig,
 		qt = qt.NewChild("rollup cache put series: query=%s, timeRange=%s, step=%d, window=%d, series=%d", query, ec.timeRangeString(), ec.Step, window, len(tss))
 		defer qt.Done()
 	}
-	if !ec.mayCache() {
-		qt.Printf("do not store series to cache, since it is disabled in the current context")
-		return
-	}
 	if len(tss) == 0 {
-		qt.Printf("do not store empty series list")
+		qt.Printf("do not cache empty series list")
 		return
 	}
 
+	if len(tss) > 1 {
+		// Verify whether tss contains series with duplicate naming.
+		// There is little sense in storing such series in the cache, since they cannot be merged in mergeSeries() later.
+		bb := bbPool.Get()
+		m := make(map[string]struct{}, len(tss))
+		for _, ts := range tss {
+			bb.B = marshalMetricNameSorted(bb.B[:0], &ts.MetricName)
+			if _, ok := m[string(bb.B)]; ok {
+				qt.Printf("do not cache series with duplicate naming %s", &ts.MetricName)
+				return
+			}
+			m[string(bb.B)] = struct{}{}
+		}
+		bbPool.Put(bb)
+	}
+
 	// Remove values up to currentTime - step - cacheTimestampOffset,
 	// since these values may be added later.
 	timestamps := tss[0].Timestamps
@@ -523,7 +531,7 @@ func mustSaveRollupResultCacheKeyPrefix(path string) {
 var tooBigRollupResults = metrics.NewCounter("vm_too_big_rollup_results_total")
 
 // Increment this value every time the format of the cache changes.
-const rollupResultCacheVersion = 10
+const rollupResultCacheVersion = 11
 
 const (
 	rollupResultCacheTypeSeries        = 0
@@ -564,74 +572,115 @@ func marshalTagFiltersForRollupResultCacheKey(dst []byte, etfs [][]storage.TagFi
 	return dst
 }
 
-// mergeTimeseries concatenates b with a and returns the result.
+func equalTimestamps(a, b []int64) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	for i, tsA := range a {
+		tsB := b[i]
+		if tsA != tsB {
+			return false
+		}
+	}
+	return true
+}
+
+// mergeSeries concatenates a with b and returns the result.
+//
+// true is returned on successful concatenation, false otherwise.
 //
 // Preconditions:
-// - a mustn't intersect with b by timestamps.
-// - a timestamps must be smaller than b timestamps.
+// - bStart must be in the range [ec.Start .. ec.End]
+// - a must contain series with all the samples on the range [ec.Start ... bStart - ec.Step] with ec.Step interval between them
+// - b must contain series with all the samples on the range [bStart .. ec.End] with ec.Step interval between them
 //
 // Postconditions:
+// - the returned series contain all the samples on the range [ec.Start .. ec.End] with ec.Step interval between them
 // - a and b cannot be used after returning from the call.
-func mergeTimeseries(qt *querytracer.Tracer, a, b []*timeseries, bStart int64, ec *EvalConfig) ([]*timeseries, error) {
-	qt = qt.NewChild("merge series len(a)=%d, len(b)=%d", len(a), len(b))
-	defer qt.Done()
+func mergeSeries(qt *querytracer.Tracer, a, b []*timeseries, bStart int64, ec *EvalConfig) ([]*timeseries, bool) {
+	if qt.Enabled() {
+		qt = qt.NewChild("merge series on time range %s with step=%dms; len(a)=%d, len(b)=%d, bStart=%s",
+			ec.timeRangeString(), ec.Step, len(a), len(b), storage.TimestampToHumanReadableFormat(bStart))
+		defer qt.Done()
+	}
 
 	sharedTimestamps := ec.getSharedTimestamps()
-	if bStart == ec.Start {
-		// Nothing to merge - b covers all the time range.
-		// Verify b is correct.
+	i := 0
+	for i < len(sharedTimestamps) && sharedTimestamps[i] < bStart {
+		i++
+	}
+	aTimestamps := sharedTimestamps[:i]
+	bTimestamps := sharedTimestamps[i:]
+
+	if len(bTimestamps) == len(sharedTimestamps) {
+		// Nothing to merge - just return b to the caller
 		for _, tsB := range b {
+			if !equalTimestamps(tsB.Timestamps, bTimestamps) {
+				logger.Panicf("BUG: invalid timestamps in b series %s; got %d; want %d", &tsB.MetricName, tsB.Timestamps, bTimestamps)
+			}
 			tsB.denyReuse = true
 			tsB.Timestamps = sharedTimestamps
-			if len(tsB.Values) != len(tsB.Timestamps) {
-				logger.Panicf("BUG: unexpected number of values in b; got %d; want %d", len(tsB.Values), len(tsB.Timestamps))
-			}
 		}
-		return b, nil
+		return b, true
 	}
 
-	m := make(map[string]*timeseries, len(a))
 	bb := bbPool.Get()
 	defer bbPool.Put(bb)
+
+	mA := make(map[string]*timeseries, len(a))
 	for _, ts := range a {
+		if !equalTimestamps(ts.Timestamps, aTimestamps) {
+			logger.Panicf("BUG: invalid timestamps in a series %s; got %d; want %d", &ts.MetricName, ts.Timestamps, aTimestamps)
+		}
 		bb.B = marshalMetricNameSorted(bb.B[:0], &ts.MetricName)
-		if _, ok := m[string(bb.B)]; ok {
-			return nil, fmt.Errorf("duplicate series found: %s", &ts.MetricName)
+		if _, ok := mA[string(bb.B)]; ok {
+			qt.Printf("cannot merge series because a series contain duplicate %s", &ts.MetricName)
+			return nil, false
 		}
-		m[string(bb.B)] = ts
+		mA[string(bb.B)] = ts
 	}
 
+	mB := make(map[string]struct{}, len(b))
 	rvs := make([]*timeseries, 0, len(a))
+	var aNaNs []float64
 	for _, tsB := range b {
+		if !equalTimestamps(tsB.Timestamps, bTimestamps) {
+			logger.Panicf("BUG: invalid timestamps for b series %s; got %d; want %d", &tsB.MetricName, tsB.Timestamps, bTimestamps)
+		}
+		bb.B = marshalMetricNameSorted(bb.B[:0], &tsB.MetricName)
+		if _, ok := mB[string(bb.B)]; ok {
+			qt.Printf("cannot merge series because b series contain duplicate %s", &tsB.MetricName)
+			return nil, false
+		}
+		mB[string(bb.B)] = struct{}{}
+
 		var tmp timeseries
 		tmp.denyReuse = true
 		tmp.Timestamps = sharedTimestamps
 		tmp.Values = make([]float64, 0, len(tmp.Timestamps))
 		tmp.MetricName.MoveFrom(&tsB.MetricName)
 
-		bb.B = marshalMetricNameSorted(bb.B[:0], &tmp.MetricName)
-		k := string(bb.B)
-		tsA := m[k]
+		tsA := mA[string(bb.B)]
 		if tsA == nil {
-			tStart := ec.Start
-			for tStart < bStart {
-				tmp.Values = append(tmp.Values, nan)
-				tStart += ec.Step
+			if aNaNs == nil {
+				tStart := ec.Start
+				for tStart < bStart {
+					aNaNs = append(aNaNs, nan)
+					tStart += ec.Step
+				}
 			}
+			tmp.Values = append(tmp.Values, aNaNs...)
 		} else {
 			tmp.Values = append(tmp.Values, tsA.Values...)
-			delete(m, k)
+			delete(mA, string(bb.B))
 		}
 		tmp.Values = append(tmp.Values, tsB.Values...)
-		if len(tmp.Values) != len(tmp.Timestamps) {
-			logger.Panicf("BUG: unexpected values after merging new values; got %d; want %d; len(a.Values)=%d; len(b.Values)=%d",
-				len(tmp.Values), len(tmp.Timestamps), len(tsA.Values), len(tsB.Values))
-		}
 		rvs = append(rvs, &tmp)
 	}
 
-	// Copy the remaining timeseries from m.
-	for _, tsA := range m {
+	// Copy the remaining timeseries from mA.
+	var bNaNs []float64
+	for _, tsA := range mA {
 		var tmp timeseries
 		tmp.denyReuse = true
 		tmp.Timestamps = sharedTimestamps
@@ -639,18 +688,18 @@ func mergeTimeseries(qt *querytracer.Tracer, a, b []*timeseries, bStart int64, e
 		tmp.MetricName.MoveFrom(&tsA.MetricName)
 		tmp.Values = append(tmp.Values, tsA.Values...)
 
-		tStart := bStart
-		for tStart <= ec.End {
-			tmp.Values = append(tmp.Values, nan)
-			tStart += ec.Step
-		}
-		if len(tmp.Values) != len(tmp.Timestamps) {
-			logger.Panicf("BUG: unexpected values in the result after adding cached values; got %d; want %d", len(tmp.Values), len(tmp.Timestamps))
+		if bNaNs == nil {
+			tStart := bStart
+			for tStart <= ec.End {
+				bNaNs = append(bNaNs, nan)
+				tStart += ec.Step
+			}
 		}
+		tmp.Values = append(tmp.Values, bNaNs...)
 		rvs = append(rvs, &tmp)
 	}
 	qt.Printf("resulting series=%d", len(rvs))
-	return rvs, nil
+	return rvs, true
 }
 
 type rollupResultCacheMetainfo struct {
@@ -733,9 +782,9 @@ func (mi *rollupResultCacheMetainfo) AddKey(key rollupResultCacheKey, start, end
 		end:   end,
 		key:   key,
 	})
-	if len(mi.entries) > 30 {
+	if len(mi.entries) > 10 {
 		// Remove old entries.
-		mi.entries = append(mi.entries[:0], mi.entries[10:]...)
+		mi.entries = append(mi.entries[:0], mi.entries[5:]...)
 	}
 }
 
diff --git a/app/vmui/packages/vmui/src/components/Configurators/AdditionalSettings/AdditionalSettings.tsx b/app/vmui/packages/vmui/src/components/Configurators/AdditionalSettings/AdditionalSettings.tsx
index 9ccb8bde3..8bf00aeec 100644
--- a/app/vmui/packages/vmui/src/components/Configurators/AdditionalSettings/AdditionalSettings.tsx
+++ b/app/vmui/packages/vmui/src/components/Configurators/AdditionalSettings/AdditionalSettings.tsx
@@ -33,8 +33,8 @@ const AdditionalSettingsControls: FC<{isMobile?: boolean}> = ({ isMobile }) => {
   };
 
   const handleKeyDown = (e: KeyboardEvent) => {
-    const { key, ctrlKey, metaKey, shiftKey } = e;
-    if (key === "a" && shiftKey && (ctrlKey || metaKey)) {
+    const { code, altKey } = e;
+    if (code === "KeyA" && altKey) {
       e.preventDefault();
       onChangeAutocomplete();
     }
diff --git a/app/vmui/packages/vmui/src/components/Main/ShortcutKeys/constants/keyList.tsx b/app/vmui/packages/vmui/src/components/Main/ShortcutKeys/constants/keyList.tsx
index 5d9f56d37..5072beaa8 100644
--- a/app/vmui/packages/vmui/src/components/Main/ShortcutKeys/constants/keyList.tsx
+++ b/app/vmui/packages/vmui/src/components/Main/ShortcutKeys/constants/keyList.tsx
@@ -4,8 +4,9 @@ import { VisibilityIcon } from "../../Icons";
 import GraphTips from "../../../Chart/GraphTips/GraphTips";
 
 const ctrlMeta = <code>{isMacOs() ? "Cmd" : "Ctrl"}</code>;
+const altMeta = <code>{isMacOs() ? "Option" : "Alt"}</code>;
 
-export const AUTOCOMPLETE_KEY = <>{ctrlMeta} + <code>Shift</code> + <code>A</code></>;
+export const AUTOCOMPLETE_KEY = <>{altMeta} + <code>A</code></>;
 
 const keyList = [
   {
diff --git a/dashboards/victoriametrics-cluster.json b/dashboards/victoriametrics-cluster.json
index be8bc93fc..72d65274b 100644
--- a/dashboards/victoriametrics-cluster.json
+++ b/dashboards/victoriametrics-cluster.json
@@ -76,11 +76,11 @@
           "uid": "$ds"
         },
         "enable": true,
-        "expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(short_version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset 20m) by(short_version))",
+        "expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset 20m) by(version))",
         "hide": true,
         "iconColor": "dark-blue",
         "name": "version change",
-        "textFormat": "{{short_version}}",
+        "textFormat": "{{version}}",
         "titleFormat": "Version change"
       },
       {
diff --git a/dashboards/victoriametrics.json b/dashboards/victoriametrics.json
index 5e75a74a0..4460ba961 100644
--- a/dashboards/victoriametrics.json
+++ b/dashboards/victoriametrics.json
@@ -76,11 +76,11 @@
           "uid": "$ds"
         },
         "enable": true,
-        "expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(short_version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset 20m) by(short_version))",
+        "expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset 20m) by(short_versionversion))",
         "hide": true,
         "iconColor": "dark-blue",
         "name": "version",
-        "textFormat": "{{short_version}}",
+        "textFormat": "{{version}}",
         "titleFormat": "Version change"
       }
     ]
diff --git a/dashboards/vm/victoriametrics-cluster.json b/dashboards/vm/victoriametrics-cluster.json
index c6395c378..a6589aad5 100644
--- a/dashboards/vm/victoriametrics-cluster.json
+++ b/dashboards/vm/victoriametrics-cluster.json
@@ -77,11 +77,11 @@
           "uid": "$ds"
         },
         "enable": true,
-        "expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(short_version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset 20m) by(short_version))",
+        "expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset 20m) by(version))",
         "hide": true,
         "iconColor": "dark-blue",
         "name": "version change",
-        "textFormat": "{{short_version}}",
+        "textFormat": "{{version}}",
         "titleFormat": "Version change"
       },
       {
diff --git a/dashboards/vm/victoriametrics.json b/dashboards/vm/victoriametrics.json
index 991f6f77c..2b36b9c41 100644
--- a/dashboards/vm/victoriametrics.json
+++ b/dashboards/vm/victoriametrics.json
@@ -77,11 +77,11 @@
           "uid": "$ds"
         },
         "enable": true,
-        "expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(short_version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset 20m) by(short_version))",
+        "expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset 20m) by(short_versionversion))",
         "hide": true,
         "iconColor": "dark-blue",
         "name": "version",
-        "textFormat": "{{short_version}}",
+        "textFormat": "{{version}}",
         "titleFormat": "Version change"
       }
     ]
diff --git a/docs/Articles.md b/docs/Articles.md
index 2012882d8..b5f68ab36 100644
--- a/docs/Articles.md
+++ b/docs/Articles.md
@@ -82,6 +82,7 @@ See also [case studies](https://docs.victoriametrics.com/CaseStudies.html).
 * [VictoriaMetrics: VMAuth – Proxy, Authentication, and Authorization](https://rtfm.co.ua/en/victoriametrics-vmauth-proxy-authentication-and-authorization/)
 * [Solving metrics at scale with VictoriaMetrics](https://sarthak-acoustic.medium.com/solving-metrics-at-scale-with-victoriametrics-ac9c306826c3)
 * [VictoriaMetrics: a comprehensive guide](https://medium.com/@seifeddinerajhi/victoriametrics-a-comprehensive-guide-comparing-it-to-prometheus-and-implementing-kubernetes-03eb8feb0cc2)
+* [Unleashing VM histograms for Ruby: Migrating from Prometheus to VictoriaMetrics with vm-client](https://hackernoon.com/unleashing-vm-histograms-for-ruby-migrating-from-prometheus-to-victoriametrics-with-vm-client)
 
 ## Our articles
 
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 2623b79c5..bf40ba46d 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -28,6 +28,10 @@ The sandbox cluster installation is running under the constant load generated by
 
 ## tip
 
+* FEATURE: dashboards: use `version` instead of `short_version` in version change annotation for single/cluster dashboards. The update should reflect version changes even if different flavours of the same release were applied (custom builds).
+
+* BUGFIX: fix a bug, which could result in improper results and/or to `cannot merge series: duplicate series found` error during [range query](https://docs.victoriametrics.com/keyConcepts.html#range-query) execution. The issue has been introduced in [v1.95.0](https://docs.victoriametrics.com/CHANGELOG.html#v1950). See [this bugreport](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5332) for details.
+
 ## [v1.95.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.95.0)
 
 Released at 2023-11-15
diff --git a/docs/README.md b/docs/README.md
index d92530592..c7bb6ee67 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -90,6 +90,7 @@ VictoriaMetrics has the following prominent features:
   * [Arbitrary CSV data](#how-to-import-csv-data).
   * [Native binary format](#how-to-import-data-in-native-format).
   * [DataDog agent or DogStatsD](#how-to-send-data-from-datadog-agent).
+  * [NewRelic infrastructure agent](#how-to-send-data-from-newrelic-agent).
   * [OpenTelemetry metrics format](#sending-data-via-opentelemetry).
 * It supports powerful [stream aggregation](https://docs.victoriametrics.com/stream-aggregation.html), which can be used as a [statsd](https://github.com/statsd/statsd) alternative.
 * It supports metrics [relabeling](#relabeling).
diff --git a/docs/Release-Guide.md b/docs/Release-Guide.md
index 6ebfec621..e9979d11d 100644
--- a/docs/Release-Guide.md
+++ b/docs/Release-Guide.md
@@ -143,10 +143,10 @@ The operator repository [https://github.com/VictoriaMetrics/operator/](https://g
   - `vmbackupmanager` in `BaseOperatorConf.VMBackup.Version` (should be enterprise version),
   - `vmauth` in `BaseOperatorConf.VMAuthDefault.Version`.
 - Run `make operator-conf`.
-- Rename "Next release" section in `CHANGELOG.md` to the *new release version* and create new empty "Next release" section.
+- Rename "Next release" section in `docs/CHANGELOG.md` to the *new release version* and create new empty "Next release" section.
 - Commit and push changes to `master`.
 - Create and push a new tag with the *new release version*.
-- Create github release from this tag with "Release notes" from `CHANGELOG` for this version in description.
+- Create github release from this tag with "Release notes" from `docs/CHANGELOG.md` for this version in description.
 
 ## Helm Charts
 
diff --git a/docs/Single-server-VictoriaMetrics.md b/docs/Single-server-VictoriaMetrics.md
index 1088f2492..97ec5ff6d 100644
--- a/docs/Single-server-VictoriaMetrics.md
+++ b/docs/Single-server-VictoriaMetrics.md
@@ -98,6 +98,7 @@ VictoriaMetrics has the following prominent features:
   * [Arbitrary CSV data](#how-to-import-csv-data).
   * [Native binary format](#how-to-import-data-in-native-format).
   * [DataDog agent or DogStatsD](#how-to-send-data-from-datadog-agent).
+  * [NewRelic infrastructure agent](#how-to-send-data-from-newrelic-agent).
   * [OpenTelemetry metrics format](#sending-data-via-opentelemetry).
 * It supports powerful [stream aggregation](https://docs.victoriametrics.com/stream-aggregation.html), which can be used as a [statsd](https://github.com/statsd/statsd) alternative.
 * It supports metrics [relabeling](#relabeling).
diff --git a/docs/grafana-datasource.md b/docs/grafana-datasource.md
index 151ba964e..d573d79c7 100644
--- a/docs/grafana-datasource.md
+++ b/docs/grafana-datasource.md
@@ -11,7 +11,7 @@ aliases:
 ---
 
 # VictoriaMetrics datasource for Grafana
-The [VictoriaMetrics](http://docs.victoriametrics.com/) datasource plugin allows you to query and visualize 
+The [VictoriaMetrics](http://docs.victoriametrics.com/) datasource plugin allows you to query and visualize
 data from VictoriaMetrics in Grafana.
 
 * [Motivation](#motivation)
@@ -23,10 +23,10 @@ data from VictoriaMetrics in Grafana.
 
 ## Motivation
 
-Thanks to VictoriaMetrics compatibility with Prometheus API users can use 
+Thanks to VictoriaMetrics compatibility with Prometheus API users can use
 [Prometheus datasource](https://docs.victoriametrics.com/#grafana-setup) for Grafana to query data from VictoriaMetrics.
 But with time, Prometheus and VictoriaMetrics diverge more and more. After some unexpected changes to Prometheus datasource
-we decided to create a datasource plugin specifically for VictoriaMetrics. 
+we decided to create a datasource plugin specifically for VictoriaMetrics.
 The benefits of using VictoriaMetrics plugin are the following:
 
 * [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html) functions support;
@@ -44,6 +44,14 @@ the following changes to Grafana's `grafana.ini` config:
 allow_loading_unsigned_plugins = victoriametrics-datasource
 ```
 
+For `grafana-operator` users, please adjust `config:` section in your `kind=Grafana` resource as below
+
+```
+  config:
+    plugins:
+      allow_loading_unsigned_plugins: "victoriametrics-datasource"
+```
+
 See [why VictoriaMetrics datasource is unsigned](#why-victoriaMetrics-datasource-is-unsigned).
 
 For detailed instructions on how to install the plugin on Grafana Cloud or
@@ -51,7 +59,7 @@ locally, please checkout the [Plugin installation docs](https://grafana.com/docs
 
 ### Grafana Provisioning
 
-Provision of Grafana plugin requires to create 
+Provision of Grafana plugin requires to create
 [datasource config file](http://docs.grafana.org/administration/provisioning/#datasources).
 
 Example of config file for provisioning VictoriaMetrics datasource is the following:
@@ -62,7 +70,7 @@ apiVersion: 1
 # List of data sources to insert/update depending on what's
 # available in the database.
 datasources:
-   # <string, required> Name of the VictoriaMetrics datasource 
+   # <string, required> Name of the VictoriaMetrics datasource
    # displayed in Grafana panels and queries.
    - name: VictoriaMetrics
       # <string, required> Sets the data source type.
@@ -74,11 +82,11 @@ datasources:
      access: proxy
      # <string> Sets default URL of the single node version of VictoriaMetrics
      url: http://victoriametrics:8428
-     # <string> Sets the pre-selected datasource for new panels. 
+     # <string> Sets the pre-selected datasource for new panels.
      # You can set only one default data source per organization.
      isDefault: true
 
-     # <string, required> Name of the VictoriaMetrics datasource 
+     # <string, required> Name of the VictoriaMetrics datasource
      # displayed in Grafana panels and queries.
    - name: VictoriaMetrics - cluster
      # <string, required> Sets the data source type.
@@ -90,7 +98,7 @@ datasources:
      access: proxy
      # <string> Sets default URL of the cluster version of VictoriaMetrics
      url: http://vmselect:8481/select/0/prometheus
-     # <string> Sets the pre-selected datasource for new panels. 
+     # <string> Sets the pre-selected datasource for new panels.
      # You can set only one default data source per organization.
      isDefault: false
 ```
@@ -164,10 +172,14 @@ extraInitContainers:
        tar -xf /var/lib/grafana/plugins/plugin.tar.gz -C /var/lib/grafana/plugins/
        rm /var/lib/grafana/plugins/plugin.tar.gz
     volumeMounts:
+      # For grafana-operator users, change `name: storage` to `name: grafana-data`
       - name: storage
         mountPath: /var/lib/grafana
 ```
 
+For `grafana-operator` users, the above configuration should be done for the part
+`/spec/deployment/spec/template/spec/initContainers` of your `kind=Grafana` resource.
+
 This example uses init container to download and install plugin. To allow Grafana using this container as a sidecar
 set the following config:
 ```yaml
@@ -294,13 +306,13 @@ make victorimetrics-frontend-plugin-build
 This command will build all frontend app into `dist` folder.
 
 ### 5. How to build frontend and backend parts of the plugin:
-When frontend and backend parts of the plugin is required, run the following command from 
+When frontend and backend parts of the plugin is required, run the following command from
 the root folder of the project:
 ```
 make victoriametrics-datasource-plugin-build
 ```
-This command will build frontend part and backend part or the plugin and locate both 
-parts into `dist` folder. 
+This command will build frontend part and backend part or the plugin and locate both
+parts into `dist` folder.
 
 ## How to use WITH templates
 
@@ -313,16 +325,16 @@ WITH expressions are stored in the datasource object. If the dashboard gets expo
 
 ### Defining WITH Expressions
 
-1. Navigate to the dashboard where you want to add a template.<br/> 
+1. Navigate to the dashboard where you want to add a template.<br/>
    *Note: templates are available within the dashboard scope.*
 1. Click the `WITH templates` button.
 1. Enter the expression in the input field. Once done, press the `Save` button to apply the changes. For example:
    ```
    commonFilters = {instance=~"$node:$port",job=~"$job"},
-   
+
    # `cpuCount` is the number of CPUs on the node
    cpuCount = count(count(node_cpu_seconds_total{commonFilters}) by (cpu)),
-   
+
    # `cpuIdle` is the sum of idle CPU cores
    cpuIdle = sum(rate(node_cpu_seconds_total{mode='idle',commonFilters}[5m]))
    ```
@@ -366,7 +378,7 @@ To view the raw query in the interface, enable the `Raw` toggle.
 
 Based on our previous experience of [developing Grafana plugins](https://grafana.com/grafana/plugins/vertamedia-clickhouse-datasource/)
 the signing procedure was a formal act. But when we tried [to sign the plugin](https://grafana.com/docs/grafana/latest/developers/plugins/publish-a-plugin/sign-a-plugin/)
-we were told by GrafanaLabs representative the plugin falls into a Commercial signature level. It matters not 
+we were told by GrafanaLabs representative the plugin falls into a Commercial signature level. It matters not
 if plugin or VictoriaMetrics itself are opensource. The announced cost of Commercial signature level was much higher
 than expected, so we interrupted the procedure.
 
diff --git a/docs/keyConcepts.md b/docs/keyConcepts.md
index c8e0a60d1..585604672 100644
--- a/docs/keyConcepts.md
+++ b/docs/keyConcepts.md
@@ -382,6 +382,8 @@ for data ingestion (aka `push protocols`):
   over HTTP, TCP and UDP.
 * [Graphite plaintext protocol](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-send-data-from-graphite-compatible-agents-such-as-statsd)
   with [tags](https://graphite.readthedocs.io/en/latest/tags.html#carbon).
+* [OpenTelemetry http API](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#sending-data-via-opentelemetry).
+* [NewRelic API](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-send-data-from-newrelic-agent).
 * [OpenTSDB put message](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#sending-data-via-telnet-put-protocol).
 * [HTTP OpenTSDB /api/put requests](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#sending-opentsdb-data-via-http-apiput-requests).
 * [JSON line format](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-import-data-in-json-line-format).
diff --git a/docs/operator/CHANGELOG.md b/docs/operator/CHANGELOG.md
index d097f8412..8d8423525 100644
--- a/docs/operator/CHANGELOG.md
+++ b/docs/operator/CHANGELOG.md
@@ -13,6 +13,11 @@ menu:
 
 ## Next release
 
+- TODO
+
+<a name="v0.39.2"></a>
+## [v0.39.2](https://github.com/VictoriaMetrics/operator/releases/tag/v0.39.2) - 15 Nov 2023
+
 ### Features
 
 - [vmoperator](./README.md): properly compare difference for `statefulSet` claimTemplate metadata. See [this commit](https://github.com/VictoriaMetrics/operator/commit/49f9c72b504582b06f72eda94055fd964a11d342) for details.
diff --git a/docs/operator/vars.md b/docs/operator/vars.md
index aebf10ae1..eb1d56986 100644
--- a/docs/operator/vars.md
+++ b/docs/operator/vars.md
@@ -10,7 +10,7 @@ menu:
 
 <!-- this doc autogenerated - don't edit it manually -->
 # Auto Generated vars for package config 
- updated at Mon Nov  6 07:44:56 UTC 2023 
+ updated at Wed Nov 15 20:22:18 UTC 2023 
 
 
 | varible name | variable default value | variable required | variable description |
@@ -20,7 +20,7 @@ menu:
 | VM_CUSTOMCONFIGRELOADERIMAGE | victoriametrics/operator:config-reloader-v0.38.0 | false | - |
 | VM_PSPAUTOCREATEENABLED | false | false | - |
 | VM_VMALERTDEFAULT_IMAGE | victoriametrics/vmalert | false | - |
-| VM_VMALERTDEFAULT_VERSION | v1.94.0 | false | - |
+| VM_VMALERTDEFAULT_VERSION | v1.95.0 | false | - |
 | VM_VMALERTDEFAULT_PORT | 8080 | false | - |
 | VM_VMALERTDEFAULT_USEDEFAULTRESOURCES | true | false | - |
 | VM_VMALERTDEFAULT_RESOURCE_LIMIT_MEM | 500Mi | false | - |
@@ -31,7 +31,7 @@ menu:
 | VM_VMALERTDEFAULT_CONFIGRELOADERMEMORY | 25Mi | false | - |
 | VM_VMALERTDEFAULT_CONFIGRELOADIMAGE | jimmidyson/configmap-reload:v0.3.0 | false | - |
 | VM_VMAGENTDEFAULT_IMAGE | victoriametrics/vmagent | false | - |
-| VM_VMAGENTDEFAULT_VERSION | v1.94.0 | false | - |
+| VM_VMAGENTDEFAULT_VERSION | v1.95.0 | false | - |
 | VM_VMAGENTDEFAULT_CONFIGRELOADIMAGE | quay.io/prometheus-operator/prometheus-config-reloader:v0.68.0 | false | - |
 | VM_VMAGENTDEFAULT_PORT | 8429 | false | - |
 | VM_VMAGENTDEFAULT_USEDEFAULTRESOURCES | true | false | - |
@@ -42,7 +42,7 @@ menu:
 | VM_VMAGENTDEFAULT_CONFIGRELOADERCPU | 100m | false | - |
 | VM_VMAGENTDEFAULT_CONFIGRELOADERMEMORY | 25Mi | false | - |
 | VM_VMSINGLEDEFAULT_IMAGE | victoriametrics/victoria-metrics | false | - |
-| VM_VMSINGLEDEFAULT_VERSION | v1.94.0 | false | - |
+| VM_VMSINGLEDEFAULT_VERSION | v1.95.0 | false | - |
 | VM_VMSINGLEDEFAULT_PORT | 8429 | false | - |
 | VM_VMSINGLEDEFAULT_USEDEFAULTRESOURCES | true | false | - |
 | VM_VMSINGLEDEFAULT_RESOURCE_LIMIT_MEM | 1500Mi | false | - |
@@ -53,14 +53,14 @@ menu:
 | VM_VMSINGLEDEFAULT_CONFIGRELOADERMEMORY | 25Mi | false | - |
 | VM_VMCLUSTERDEFAULT_USEDEFAULTRESOURCES | true | false | - |
 | VM_VMCLUSTERDEFAULT_VMSELECTDEFAULT_IMAGE | victoriametrics/vmselect | false | - |
-| VM_VMCLUSTERDEFAULT_VMSELECTDEFAULT_VERSION | v1.94.0-cluster | false | - |
+| VM_VMCLUSTERDEFAULT_VMSELECTDEFAULT_VERSION | v1.95.0-cluster | false | - |
 | VM_VMCLUSTERDEFAULT_VMSELECTDEFAULT_PORT | 8481 | false | - |
 | VM_VMCLUSTERDEFAULT_VMSELECTDEFAULT_RESOURCE_LIMIT_MEM | 1000Mi | false | - |
 | VM_VMCLUSTERDEFAULT_VMSELECTDEFAULT_RESOURCE_LIMIT_CPU | 500m | false | - |
 | VM_VMCLUSTERDEFAULT_VMSELECTDEFAULT_RESOURCE_REQUEST_MEM | 500Mi | false | - |
 | VM_VMCLUSTERDEFAULT_VMSELECTDEFAULT_RESOURCE_REQUEST_CPU | 100m | false | - |
 | VM_VMCLUSTERDEFAULT_VMSTORAGEDEFAULT_IMAGE | victoriametrics/vmstorage | false | - |
-| VM_VMCLUSTERDEFAULT_VMSTORAGEDEFAULT_VERSION | v1.94.0-cluster | false | - |
+| VM_VMCLUSTERDEFAULT_VMSTORAGEDEFAULT_VERSION | v1.95.0-cluster | false | - |
 | VM_VMCLUSTERDEFAULT_VMSTORAGEDEFAULT_VMINSERTPORT | 8400 | false | - |
 | VM_VMCLUSTERDEFAULT_VMSTORAGEDEFAULT_VMSELECTPORT | 8401 | false | - |
 | VM_VMCLUSTERDEFAULT_VMSTORAGEDEFAULT_PORT | 8482 | false | - |
@@ -69,7 +69,7 @@ menu:
 | VM_VMCLUSTERDEFAULT_VMSTORAGEDEFAULT_RESOURCE_REQUEST_MEM | 500Mi | false | - |
 | VM_VMCLUSTERDEFAULT_VMSTORAGEDEFAULT_RESOURCE_REQUEST_CPU | 250m | false | - |
 | VM_VMCLUSTERDEFAULT_VMINSERTDEFAULT_IMAGE | victoriametrics/vminsert | false | - |
-| VM_VMCLUSTERDEFAULT_VMINSERTDEFAULT_VERSION | v1.94.0-cluster | false | - |
+| VM_VMCLUSTERDEFAULT_VMINSERTDEFAULT_VERSION | v1.95.0-cluster | false | - |
 | VM_VMCLUSTERDEFAULT_VMINSERTDEFAULT_PORT | 8480 | false | - |
 | VM_VMCLUSTERDEFAULT_VMINSERTDEFAULT_RESOURCE_LIMIT_MEM | 500Mi | false | - |
 | VM_VMCLUSTERDEFAULT_VMINSERTDEFAULT_RESOURCE_LIMIT_CPU | 500m | false | - |
@@ -88,7 +88,7 @@ menu:
 | VM_VMALERTMANAGER_RESOURCE_REQUEST_CPU | 30m | false | - |
 | VM_DISABLESELFSERVICESCRAPECREATION | false | false | - |
 | VM_VMBACKUP_IMAGE | victoriametrics/vmbackupmanager | false | - |
-| VM_VMBACKUP_VERSION | v1.94.0-enterprise | false | - |
+| VM_VMBACKUP_VERSION | v1.95.0-enterprise | false | - |
 | VM_VMBACKUP_PORT | 8300 | false | - |
 | VM_VMBACKUP_USEDEFAULTRESOURCES | true | false | - |
 | VM_VMBACKUP_RESOURCE_LIMIT_MEM | 500Mi | false | - |
@@ -97,7 +97,7 @@ menu:
 | VM_VMBACKUP_RESOURCE_REQUEST_CPU | 150m | false | - |
 | VM_VMBACKUP_LOGLEVEL | INFO | false | - |
 | VM_VMAUTHDEFAULT_IMAGE | victoriametrics/vmauth | false | - |
-| VM_VMAUTHDEFAULT_VERSION | v1.94.0 | false | - |
+| VM_VMAUTHDEFAULT_VERSION | v1.95.0 | false | - |
 | VM_VMAUTHDEFAULT_CONFIGRELOADIMAGE | quay.io/prometheus-operator/prometheus-config-reloader:v0.68.0 | false | - |
 | VM_VMAUTHDEFAULT_PORT | 8427 | false | - |
 | VM_VMAUTHDEFAULT_USEDEFAULTRESOURCES | true | false | - |
diff --git a/docs/vmagent.md b/docs/vmagent.md
index 2c962288c..45f7db7a1 100644
--- a/docs/vmagent.md
+++ b/docs/vmagent.md
@@ -106,6 +106,7 @@ additionally to pull-based Prometheus-compatible targets' scraping:
 * InfluxDB line protocol via `http://<vmagent>:8429/write`. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-send-data-from-influxdb-compatible-agents-such-as-telegraf).
 * Graphite plaintext protocol if `-graphiteListenAddr` command-line flag is set. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-send-data-from-graphite-compatible-agents-such-as-statsd).
 * OpenTelemetry http API. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#sending-data-via-opentelemetry).
+* NewRelic API. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-send-data-from-newrelic-agent).
 * OpenTSDB telnet and http protocols if `-opentsdbListenAddr` command-line flag is set. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-send-data-from-opentsdb-compatible-agents).
 * Prometheus remote write protocol via `http://<vmagent>:8429/api/v1/write`.
 * JSON lines import protocol via `http://<vmagent>:8429/api/v1/import`. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-import-data-in-json-line-format).
@@ -187,8 +188,8 @@ to the same second-level `vmagent` instance, so they are aggregated properly.
 If `-remoteWrite.shardByURL` command-line flag is set, then all the metric labels are used for even sharding
 among remote storage systems specified in `-remoteWrite.url`. Sometimes it may be needed to use only a particular
 set of labels for sharding. For example, it may be needed to route all the metrics with the same `instance` label
-to the same `-remoteWrite.url`. In this case you can specify comma-separated list of these labels in the `-remoteWrite.shardByURLLabels`
-command-line flag. For example, `-remoteWrite.shardByURLLabels=instance,__name__` would shard metrics with the same name and `instance`
+to the same `-remoteWrite.url`. In this case you can specify comma-separated list of these labels in the `-remoteWrite.shardByURL.labels`
+command-line flag. For example, `-remoteWrite.shardByURL.labels=instance,__name__` would shard metrics with the same name and `instance`
 label to the same `-remoteWrite.url`.
 
 See also [how to scrape big number of targets](#scraping-big-number-of-targets).
diff --git a/docs/vmalert.md b/docs/vmalert.md
index 8605e37d4..63c391641 100644
--- a/docs/vmalert.md
+++ b/docs/vmalert.md
@@ -553,10 +553,20 @@ Alertmanagers.
 
 To avoid recording rules results and alerts state duplication in VictoriaMetrics server
 don't forget to configure [deduplication](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#deduplication).
-The recommended value for `-dedup.minScrapeInterval` must be multiple of vmalert's `-evaluationInterval`.
+Multiple equally configured vmalerts should evaluate rules at the same timestamps, so it is recommended 
+to set `-dedup.minScrapeInterval` as equal to vmalert's `-evaluationInterval`.
+
+If you have multiple different `interval` params for distinct rule groups, then set `-dedup.minScrapeInterval` to
+the biggest `interval` value, or value which will be a multiple for all `interval` values. For example, if you have
+two groups with `interval: 10s` and `interval: 15s`, then set `-dedup.minScrapeInterval=30s`. This would consistently
+keep only a single data point on 30s time interval for all rules. However, try to avoid having inconsistent `interval`
+values.
+
+It is not recommended having `-dedup.minScrapeInterval` smaller than `-evaluationInterval`, as it may produce 
+results with inconsistent intervals between data points.
 
 Alertmanager will automatically deduplicate alerts with identical labels, so ensure that
-all `vmalert`s are having the same config.
+all `vmalert`s are having identical config.
 
 Don't forget to configure [cluster mode](https://prometheus.io/docs/alerting/latest/alertmanager/)
 for Alertmanagers for better reliability. List all Alertmanager URLs in vmalert `-notifier.url`
diff --git a/docs/vmbackup.md b/docs/vmbackup.md
index c3a858563..3b22a91f7 100644
--- a/docs/vmbackup.md
+++ b/docs/vmbackup.md
@@ -276,7 +276,7 @@ You have to add a custom url endpoint via flag:
       -customS3Endpoint=https://s3-fips.us-gov-west-1.amazonaws.com
     ```
 
-### Permanent deletion of objects in S3 and compatible storages
+### Permanent deletion of objects in S3-compatible storages
 
 `vmbackup` and [vmbackupmanager](https://docs.victoriametrics.com/vmbackupmanager.html) use standard delete operation
 for S3-compatible object storage when pefrorming [incremental backups](#incremental-backups).
diff --git a/docs/vmbackupmanager.md b/docs/vmbackupmanager.md
index 0f8485560..076fead16 100644
--- a/docs/vmbackupmanager.md
+++ b/docs/vmbackupmanager.md
@@ -137,7 +137,7 @@ Backup retention policy is controlled by:
 
 > *Note*: 0 value in every keepLast flag results into deletion of ALL backups for particular type (hourly, daily, weekly and monthly)
 
-> *Note*: retention policy does not enforce removing previous versions of objects in object storages such if versioning is enabled. See [these docs](https://docs.victoriametrics.com/vmbackup.html#permanent-deletion-of-objects-in-s3-and-compatible-storages) for more details.
+> *Note*: retention policy does not enforce removing previous versions of objects in object storages such if versioning is enabled. See [these docs](https://docs.victoriametrics.com/vmbackup.html#permanent-deletion-of-objects-in-s3-compatible-storages) for more details.
 
 Let’s assume we have a backup manager collecting daily backups for the past 10 days.
 


Test patch:
diff --git a/app/vmselect/promql/rollup_result_cache_test.go b/app/vmselect/promql/rollup_result_cache_test.go
index 3bac0deb1..7b11f9799 100644
--- a/app/vmselect/promql/rollup_result_cache_test.go
+++ b/app/vmselect/promql/rollup_result_cache_test.go
@@ -1,6 +1,7 @@
 package promql
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
@@ -245,6 +246,7 @@ func TestRollupResultCache(t *testing.T) {
 				Timestamps: []int64{1000, 1200, 1400, 1600, 1800, 2000},
 				Values:     []float64{1, 2, 3, 4, 5, 6},
 			}
+			ts.MetricName.MetricGroup = []byte(fmt.Sprintf("metric %d", i))
 			tss = append(tss, ts)
 		}
 		rollupResultCacheV.PutSeries(nil, ec, fe, window, tss)
@@ -255,6 +257,29 @@ func TestRollupResultCache(t *testing.T) {
 		testTimeseriesEqual(t, tssResult, tss)
 	})
 
+	// Store series with identical naming (they shouldn't be stored)
+	t.Run("duplicate-series", func(t *testing.T) {
+		ResetRollupResultCache()
+		tss := []*timeseries{
+			{
+				Timestamps: []int64{800, 1000, 1200},
+				Values:     []float64{0, 1, 2},
+			},
+			{
+				Timestamps: []int64{800, 1000, 1200},
+				Values:     []float64{0, 1, 2},
+			},
+		}
+		rollupResultCacheV.PutSeries(nil, ec, fe, window, tss)
+		tssResult, newStart := rollupResultCacheV.GetSeries(nil, ec, fe, window)
+		if newStart != ec.Start {
+			t.Fatalf("unexpected newStart; got %d; want %d", newStart, ec.Start)
+		}
+		if len(tssResult) != 0 {
+			t.Fatalf("unexpected non-empty series returned")
+		}
+	})
+
 	// Store multiple time series
 	t.Run("multi-timeseries", func(t *testing.T) {
 		ResetRollupResultCache()
@@ -294,7 +319,7 @@ func TestRollupResultCache(t *testing.T) {
 
 }
 
-func TestMergeTimeseries(t *testing.T) {
+func TestMergeSeries(t *testing.T) {
 	ec := &EvalConfig{
 		Start:              1000,
 		End:                2000,
@@ -311,9 +336,9 @@ func TestMergeTimeseries(t *testing.T) {
 				Values:     []float64{1, 2, 3, 4, 5, 6},
 			},
 		}
-		tss, err := mergeTimeseries(nil, a, b, 1000, ec)
-		if err != nil {
-			t.Fatalf("unexpected error: %s", err)
+		tss, ok := mergeSeries(nil, a, b, 1000, ec)
+		if !ok {
+			t.Fatalf("unexpected failure to merge series")
 		}
 		tssExpected := []*timeseries{
 			{
@@ -331,9 +356,9 @@ func TestMergeTimeseries(t *testing.T) {
 				Values:     []float64{3, 4, 5, 6},
 			},
 		}
-		tss, err := mergeTimeseries(nil, a, b, bStart, ec)
-		if err != nil {
-			t.Fatalf("unexpected error: %s", err)
+		tss, ok := mergeSeries(nil, a, b, bStart, ec)
+		if !ok {
+			t.Fatalf("unexpected failure to merge series")
 		}
 		tssExpected := []*timeseries{
 			{
@@ -351,9 +376,9 @@ func TestMergeTimeseries(t *testing.T) {
 			},
 		}
 		b := []*timeseries{}
-		tss, err := mergeTimeseries(nil, a, b, bStart, ec)
-		if err != nil {
-			t.Fatalf("unexpected error: %s", err)
+		tss, ok := mergeSeries(nil, a, b, bStart, ec)
+		if !ok {
+			t.Fatalf("unexpected failure to merge series")
 		}
 		tssExpected := []*timeseries{
 			{
@@ -376,9 +401,9 @@ func TestMergeTimeseries(t *testing.T) {
 				Values:     []float64{3, 4, 5, 6},
 			},
 		}
-		tss, err := mergeTimeseries(nil, a, b, bStart, ec)
-		if err != nil {
-			t.Fatalf("unexpected error: %s", err)
+		tss, ok := mergeSeries(nil, a, b, bStart, ec)
+		if !ok {
+			t.Fatalf("unexpected failure to merge series")
 		}
 		tssExpected := []*timeseries{
 			{
@@ -403,9 +428,9 @@ func TestMergeTimeseries(t *testing.T) {
 			},
 		}
 		b[0].MetricName.MetricGroup = []byte("foo")
-		tss, err := mergeTimeseries(nil, a, b, bStart, ec)
-		if err != nil {
-			t.Fatalf("unexpected error: %s", err)
+		tss, ok := mergeSeries(nil, a, b, bStart, ec)
+		if !ok {
+			t.Fatalf("unexpected failure to merge series")
 		}
 		tssExpected := []*timeseries{
 			{
@@ -425,6 +450,52 @@ func TestMergeTimeseries(t *testing.T) {
 		}
 		testTimeseriesEqual(t, tss, tssExpected)
 	})
+	t.Run("duplicate-series-a", func(t *testing.T) {
+		a := []*timeseries{
+			{
+				Timestamps: []int64{1000, 1200},
+				Values:     []float64{2, 1},
+			},
+			{
+				Timestamps: []int64{1000, 1200},
+				Values:     []float64{3, 3},
+			},
+		}
+		b := []*timeseries{
+			{
+				Timestamps: []int64{1400, 1600, 1800, 2000},
+				Values:     []float64{3, 4, 5, 6},
+			},
+		}
+		tss, ok := mergeSeries(nil, a, b, bStart, ec)
+		if ok {
+			t.Fatalf("expecting failre to merge series")
+		}
+		testTimeseriesEqual(t, tss, nil)
+	})
+	t.Run("duplicate-series-b", func(t *testing.T) {
+		a := []*timeseries{
+			{
+				Timestamps: []int64{1000, 1200},
+				Values:     []float64{1, 2},
+			},
+		}
+		b := []*timeseries{
+			{
+				Timestamps: []int64{1400, 1600, 1800, 2000},
+				Values:     []float64{3, 4, 5, 6},
+			},
+			{
+				Timestamps: []int64{1400, 1600, 1800, 2000},
+				Values:     []float64{13, 14, 15, 16},
+			},
+		}
+		tss, ok := mergeSeries(nil, a, b, bStart, ec)
+		if ok {
+			t.Fatalf("expecting failre to merge series")
+		}
+		testTimeseriesEqual(t, tss, nil)
+	})
 }
 
 func testTimeseriesEqual(t *testing.T, tss, tssExpected []*timeseries) {