app/vmagent/remotewrite: automatically set series limits to MaxInt32 when setting value to -1 (#9614)

Automatically set daily and hourly series limits to `MaxInt32` when `remoteWrite.maxHourlySeries` or `remoteWrite.maxDailySeries` is set to `-1`.

This change addresses a usability issue with the cardinality limiter. Users may want to enable the limiter to observe its metrics before deciding on an appropriate limit. However, the underlying bloom filter only supports `int32`, so setting large values can lead to overflow.

With this PR:
* Setting either flag to `-1` is treated as “no practical limit” and internally mapped to `math.MaxInt32`
* Values exceeding `int32` are safely clamped to `MaxInt32` to prevent overflow

This allows users to enable the limiter for estimation purposes without risking invalid configurations or runtime issues.

https://github.com/VictoriaMetrics/VictoriaMetrics/pull/9614

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
Signed-off-by: Max Kotliar <kotlyar.maksim@gmail.com>
Co-authored-by: Nikolay <nik@victoriametrics.com>
Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
Zakhar Bessarab
2026-04-08 13:54:27 +04:00
committed by GitHub
parent 6337dfc472
commit fa1dd0ec0a
8 changed files with 490 additions and 20 deletions

View File

@@ -3,6 +3,7 @@ package remotewrite
import (
"flag"
"fmt"
"math"
"net/http"
"net/url"
"path/filepath"
@@ -11,6 +12,10 @@ import (
"sync/atomic"
"time"
"github.com/cespare/xxhash/v2"
"github.com/VictoriaMetrics/metrics"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bloomfilter"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
@@ -31,8 +36,6 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/slicesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/streamaggr"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeserieslimits"
"github.com/VictoriaMetrics/metrics"
"github.com/cespare/xxhash/v2"
)
var (
@@ -81,10 +84,14 @@ var (
`This may be needed for reducing memory usage at remote storage when the order of labels in incoming samples is random. `+
`For example, if m{k1="v1",k2="v2"} may be sent as m{k2="v2",k1="v1"}`+
`Enabled sorting for labels can slow down ingestion performance a bit`)
maxHourlySeries = flag.Int("remoteWrite.maxHourlySeries", 0, "The maximum number of unique series vmagent can send to remote storage systems during the last hour. "+
"Excess series are logged and dropped. This can be useful for limiting series cardinality. See https://docs.victoriametrics.com/victoriametrics/vmagent/#cardinality-limiter")
maxDailySeries = flag.Int("remoteWrite.maxDailySeries", 0, "The maximum number of unique series vmagent can send to remote storage systems during the last 24 hours. "+
"Excess series are logged and dropped. This can be useful for limiting series churn rate. See https://docs.victoriametrics.com/victoriametrics/vmagent/#cardinality-limiter")
maxHourlySeries = flag.Int64("remoteWrite.maxHourlySeries", 0, "The maximum number of unique series vmagent can send to remote storage systems during the last hour. "+
"Excess series are logged and dropped. This can be useful for limiting series cardinality. "+
fmt.Sprintf("Setting this flag to '-1' sets limit to maximum possible value (%d) which is useful in order to enable series tracking without enforcing limits. ", math.MaxInt32)+
"See https://docs.victoriametrics.com/victoriametrics/vmagent/#cardinality-limiter")
maxDailySeries = flag.Int64("remoteWrite.maxDailySeries", 0, "The maximum number of unique series vmagent can send to remote storage systems during the last 24 hours. "+
"Excess series are logged and dropped. This can be useful for limiting series churn rate. "+
fmt.Sprintf("Setting this flag to '-1' sets limit to maximum possible value (%d) which is useful in order to enable series tracking without enforcing limits. ", math.MaxInt32)+
"See https://docs.victoriametrics.com/victoriametrics/vmagent/#cardinality-limiter")
maxIngestionRate = flag.Int("maxIngestionRate", 0, "The maximum number of samples vmagent can receive per second. Data ingestion is paused when the limit is exceeded. "+
"By default there are no limits on samples ingestion rate. See also -remoteWrite.rateLimit")
@@ -160,8 +167,8 @@ func Init() {
if len(*remoteWriteURLs) == 0 {
logger.Fatalf("at least one `-remoteWrite.url` command-line flag must be set")
}
if *maxHourlySeries > 0 {
hourlySeriesLimiter = bloomfilter.NewLimiter(*maxHourlySeries, time.Hour)
if limit := getMaxHourlySeries(); limit > 0 {
hourlySeriesLimiter = bloomfilter.NewLimiter(limit, time.Hour)
_ = metrics.NewGauge(`vmagent_hourly_series_limit_max_series`, func() float64 {
return float64(hourlySeriesLimiter.MaxItems())
})
@@ -169,8 +176,8 @@ func Init() {
return float64(hourlySeriesLimiter.CurrentItems())
})
}
if *maxDailySeries > 0 {
dailySeriesLimiter = bloomfilter.NewLimiter(*maxDailySeries, 24*time.Hour)
if limit := getMaxDailySeries(); limit > 0 {
dailySeriesLimiter = bloomfilter.NewLimiter(limit, 24*time.Hour)
_ = metrics.NewGauge(`vmagent_daily_series_limit_max_series`, func() float64 {
return float64(dailySeriesLimiter.MaxItems())
})
@@ -1141,3 +1148,21 @@ func newMapFromStrings(a []string) map[string]struct{} {
}
return m
}
func getMaxHourlySeries() int {
limit := *maxHourlySeries
if limit == -1 || limit > math.MaxInt32 {
return math.MaxInt32
}
return int(limit)
}
func getMaxDailySeries() int {
limit := *maxDailySeries
if limit == -1 || limit > math.MaxInt32 {
return math.MaxInt32
}
return int(limit)
}

View File

@@ -5,6 +5,7 @@ import (
"flag"
"fmt"
"io"
"math"
"net/http"
"strconv"
"strings"
@@ -56,11 +57,13 @@ var (
denyQueriesOutsideRetention = flag.Bool("denyQueriesOutsideRetention", false, "Whether to deny queries outside the configured -retentionPeriod. "+
"When set, then /api/v1/query_range would return '503 Service Unavailable' error for queries with 'from' value outside -retentionPeriod. "+
"This may be useful when multiple data sources with distinct retentions are hidden behind query-tee")
maxHourlySeries = flag.Int("storage.maxHourlySeries", 0, "The maximum number of unique series can be added to the storage during the last hour. "+
maxHourlySeries = flag.Int64("storage.maxHourlySeries", 0, "The maximum number of unique series can be added to the storage during the last hour. "+
"Excess series are logged and dropped. This can be useful for limiting series cardinality. See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#cardinality-limiter . "+
fmt.Sprintf("Setting this flag to '-1' sets limit to maximum possible value (%d) which is useful in order to enable series tracking without enforcing limits. ", math.MaxInt32)+
"See also -storage.maxDailySeries")
maxDailySeries = flag.Int("storage.maxDailySeries", 0, "The maximum number of unique series can be added to the storage during the last 24 hours. "+
maxDailySeries = flag.Int64("storage.maxDailySeries", 0, "The maximum number of unique series can be added to the storage during the last 24 hours. "+
"Excess series are logged and dropped. This can be useful for limiting series churn rate. See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#cardinality-limiter . "+
fmt.Sprintf("Setting this flag to '-1' sets limit to maximum possible value (%d) which is useful in order to enable series tracking without enforcing limits. ", math.MaxInt32)+
"See also -storage.maxHourlySeries")
minFreeDiskSpaceBytes = flagutil.NewBytes("storage.minFreeDiskSpaceBytes", 100e6, "The minimum free disk space at -storageDataPath after which the storage stops accepting new data")
@@ -142,8 +145,8 @@ func Init(resetCacheIfNeeded func(mrs []storage.MetricRow)) {
WG = syncwg.WaitGroup{}
opts := storage.OpenOptions{
Retention: retentionPeriod.Duration(),
MaxHourlySeries: *maxHourlySeries,
MaxDailySeries: *maxDailySeries,
MaxHourlySeries: getMaxHourlySeries(),
MaxDailySeries: getMaxDailySeries(),
DisablePerDayIndex: *disablePerDayIndex,
TrackMetricNamesStats: *trackMetricNamesStats,
IDBPrefillStart: *idbPrefillStart,
@@ -603,10 +606,10 @@ func writeStorageMetrics(w io.Writer, strg *storage.Storage) {
metrics.WriteCounterUint64(w, `vm_rows_ignored_total{reason="big_timestamp"}`, m.TooBigTimestampRows)
metrics.WriteCounterUint64(w, `vm_rows_ignored_total{reason="small_timestamp"}`, m.TooSmallTimestampRows)
metrics.WriteCounterUint64(w, `vm_rows_ignored_total{reason="invalid_raw_metric_name"}`, m.InvalidRawMetricNames)
if *maxHourlySeries > 0 {
if getMaxHourlySeries() > 0 {
metrics.WriteCounterUint64(w, `vm_rows_ignored_total{reason="hourly_limit_exceeded"}`, m.HourlySeriesLimitRowsDropped)
}
if *maxDailySeries > 0 {
if getMaxDailySeries() > 0 {
metrics.WriteCounterUint64(w, `vm_rows_ignored_total{reason="daily_limit_exceeded"}`, m.DailySeriesLimitRowsDropped)
}
@@ -616,13 +619,13 @@ func writeStorageMetrics(w io.Writer, strg *storage.Storage) {
metrics.WriteCounterUint64(w, `vm_slow_row_inserts_total`, m.SlowRowInserts)
metrics.WriteCounterUint64(w, `vm_slow_per_day_index_inserts_total`, m.SlowPerDayIndexInserts)
if *maxHourlySeries > 0 {
if getMaxHourlySeries() > 0 {
metrics.WriteGaugeUint64(w, `vm_hourly_series_limit_current_series`, m.HourlySeriesLimitCurrentSeries)
metrics.WriteGaugeUint64(w, `vm_hourly_series_limit_max_series`, m.HourlySeriesLimitMaxSeries)
metrics.WriteCounterUint64(w, `vm_hourly_series_limit_rows_dropped_total`, m.HourlySeriesLimitRowsDropped)
}
if *maxDailySeries > 0 {
if getMaxDailySeries() > 0 {
metrics.WriteGaugeUint64(w, `vm_daily_series_limit_current_series`, m.DailySeriesLimitCurrentSeries)
metrics.WriteGaugeUint64(w, `vm_daily_series_limit_max_series`, m.DailySeriesLimitMaxSeries)
metrics.WriteCounterUint64(w, `vm_daily_series_limit_rows_dropped_total`, m.DailySeriesLimitRowsDropped)
@@ -747,3 +750,21 @@ func jsonResponseError(w http.ResponseWriter, err error) {
errStr := err.Error()
fmt.Fprintf(w, `{"status":"error","msg":%s}`, stringsutil.JSONString(errStr))
}
func getMaxHourlySeries() int {
limit := *maxHourlySeries
if limit == -1 || limit > math.MaxInt32 {
return math.MaxInt32
}
return int(limit)
}
func getMaxDailySeries() int {
limit := *maxDailySeries
if limit == -1 || limit > math.MaxInt32 {
return math.MaxInt32
}
return int(limit)
}

View File

@@ -1,7 +1,9 @@
package tests
import (
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
@@ -297,6 +299,132 @@ func TestSingleIngestionProtocols(t *testing.T) {
}
func TestSingleCardinalityLimiter(t *testing.T) {
waitFor := func(f func() bool) {
const (
retries = 20
period = 100 * time.Millisecond
)
t.Helper()
for i := 0; i < retries; i++ {
if f() {
return
}
time.Sleep(period)
}
t.Fatalf("timed out waiting for retry #%d", retries)
}
tc := apptest.NewTestCase(t)
defer tc.Stop()
singleHourly := tc.MustStartVmsingle("vmsingle-hourly", []string{
"-retentionPeriod=100y",
"-storage.maxHourlySeries=1",
})
singleHourly.PrometheusAPIV1ImportPrometheus(t, []string{
"foo_bar 1 1652169600000", // 2022-05-10T08:00:00Z
}, apptest.QueryOpts{})
if v := singleHourly.GetIntMetric(t, "vm_hourly_series_limit_max_series"); v != 1 {
t.Fatalf("unexpected vm_hourly_series_limit_max_series value: %d", v)
}
if v := singleHourly.GetIntMetric(t, "vm_hourly_series_limit_current_series"); v != 1 {
t.Fatalf("unexpected vm_hourly_series_limit_current_series value: %d", v)
}
if v := singleHourly.GetIntMetric(t, "vm_hourly_series_limit_rows_dropped_total"); v != 0 {
t.Fatalf("unexpected vm_hourly_series_limit_rows_dropped_total value: %d", v)
}
singleHourly.PrometheusAPIV1ImportPrometheus(t, []string{
"foo_bar2 1 1652169600000", // 2022-05-10T08:00:00Z
}, apptest.QueryOpts{})
waitFor(
func() bool {
return singleHourly.GetIntMetric(t, "vm_hourly_series_limit_rows_dropped_total") > 0
},
)
singleDaily := tc.MustStartVmsingle("vmsingle-daily", []string{
"-retentionPeriod=100y",
"-storage.maxDailySeries=1",
})
singleDaily.PrometheusAPIV1ImportPrometheus(t, []string{
"foo_bar 1 1652169600000", // 2022-05-10T08:00:00Z
}, apptest.QueryOpts{})
if v := singleDaily.GetIntMetric(t, "vm_daily_series_limit_max_series"); v != 1 {
t.Fatalf("unexpected vm_daily_series_limit_max_series value: %d", v)
}
if v := singleDaily.GetIntMetric(t, "vm_daily_series_limit_current_series"); v != 1 {
t.Fatalf("unexpected vm_daily_series_limit_current_series value: %d", v)
}
if v := singleDaily.GetIntMetric(t, "vm_daily_series_limit_rows_dropped_total"); v != 0 {
t.Fatalf("unexpected vm_daily_series_limit_rows_dropped_total value: %d", v)
}
singleDaily.PrometheusAPIV1ImportPrometheus(t, []string{
"foo_bar2 1 1652169600000", // 2022-05-10T08:00:00Z
}, apptest.QueryOpts{})
waitFor(
func() bool {
return singleDaily.GetIntMetric(t, "vm_daily_series_limit_rows_dropped_total") > 0
},
)
singleUnlimited := tc.MustStartVmsingle("vmsingle-unlimited", []string{
"-retentionPeriod=100y",
"-storage.maxHourlySeries=-1",
"-storage.maxDailySeries=-1",
})
metrics := make([]string, 0, 100)
for i := range 100 {
metrics = append(metrics, fmt.Sprintf("foo_bar%d 1 1652169600000", i)) // 2022-05-10T08:00:00Z
}
singleUnlimited.PrometheusAPIV1ImportPrometheus(t, metrics, apptest.QueryOpts{})
waitFor(
func() bool {
return singleUnlimited.GetIntMetric(t, "vm_hourly_series_limit_current_series") > 0
},
)
if v := singleUnlimited.GetIntMetric(t, "vm_hourly_series_limit_max_series"); v == 0 {
t.Fatalf("unexpected vm_hourly_series_limit_max_series value: %d", v)
}
if v := singleUnlimited.GetIntMetric(t, "vm_hourly_series_limit_current_series"); v != 100 {
t.Fatalf("unexpected vm_hourly_series_limit_current_series value: %d", v)
}
if v := singleUnlimited.GetIntMetric(t, "vm_hourly_series_limit_rows_dropped_total"); v != 0 {
t.Fatalf("unexpected vm_hourly_series_limit_rows_dropped_total value: %d", v)
}
if v := singleUnlimited.GetIntMetric(t, "vm_daily_series_limit_max_series"); v == 0 {
t.Fatalf("unexpected vm_daily_series_limit_max_series value: %d", v)
}
if v := singleUnlimited.GetIntMetric(t, "vm_daily_series_limit_current_series"); v != 100 {
t.Fatalf("unexpected vm_daily_series_limit_current_series value: %d", v)
}
if v := singleUnlimited.GetIntMetric(t, "vm_daily_series_limit_rows_dropped_total"); v != 0 {
t.Fatalf("unexpected vm_daily_series_limit_rows_dropped_total value: %d", v)
}
}
func TestClusterIngestionProtocols(t *testing.T) {
fs.MustRemoveDir(t.Name())
tc := apptest.NewTestCase(t)
@@ -591,3 +719,145 @@ func TestClusterIngestionProtocols(t *testing.T) {
})
}
func TestClusterCardinalityLimiter(t *testing.T) {
waitFor := func(f func() bool) {
const (
retries = 20
period = 100 * time.Millisecond
)
t.Helper()
for i := 0; i < retries; i++ {
if f() {
return
}
time.Sleep(period)
}
t.Fatalf("timed out waiting for retry #%d", retries)
}
tc := apptest.NewTestCase(t)
defer tc.Stop()
// Test hourly series limit
vmstorageHourly := tc.MustStartVmstorage("vmstorage-hourly", []string{
"-storageDataPath=" + tc.Dir() + "/vmstorage-hourly",
"-retentionPeriod=100y",
"-storage.maxHourlySeries=1",
})
vminsertHourly := tc.MustStartVminsert("vminsert-hourly", []string{
"-storageNode=" + vmstorageHourly.VminsertAddr(),
})
vminsertHourly.PrometheusAPIV1ImportPrometheus(t, []string{
"foo_bar 1 1652169600000", // 2022-05-10T08:00:00Z
}, apptest.QueryOpts{})
if v := vmstorageHourly.GetIntMetric(t, "vm_hourly_series_limit_max_series"); v != 1 {
t.Fatalf("unexpected vm_hourly_series_limit_max_series value: %d", v)
}
if v := vmstorageHourly.GetIntMetric(t, "vm_hourly_series_limit_current_series"); v != 1 {
t.Fatalf("unexpected vm_hourly_series_limit_current_series value: %d", v)
}
if v := vmstorageHourly.GetIntMetric(t, "vm_hourly_series_limit_rows_dropped_total"); v != 0 {
t.Fatalf("unexpected vm_hourly_series_limit_rows_dropped_total value: %d", v)
}
vminsertHourly.PrometheusAPIV1ImportPrometheus(t, []string{
"foo_bar2 1 1652169600000", // 2022-05-10T08:00:00Z
}, apptest.QueryOpts{})
waitFor(
func() bool {
return vmstorageHourly.GetIntMetric(t, "vm_hourly_series_limit_rows_dropped_total") > 0
},
)
// Test daily series limit
vmstorageDaily := tc.MustStartVmstorage("vmstorage-daily", []string{
"-storageDataPath=" + tc.Dir() + "/vmstorage-daily",
"-retentionPeriod=100y",
"-storage.maxDailySeries=1",
})
vminsertDaily := tc.MustStartVminsert("vminsert-daily", []string{
"-storageNode=" + vmstorageDaily.VminsertAddr(),
})
vminsertDaily.PrometheusAPIV1ImportPrometheus(t, []string{
"foo_bar 1 1652169600000", // 2022-05-10T08:00:00Z
}, apptest.QueryOpts{})
if v := vmstorageDaily.GetIntMetric(t, "vm_daily_series_limit_max_series"); v != 1 {
t.Fatalf("unexpected vm_daily_series_limit_max_series value: %d", v)
}
if v := vmstorageDaily.GetIntMetric(t, "vm_daily_series_limit_current_series"); v != 1 {
t.Fatalf("unexpected vm_daily_series_limit_current_series value: %d", v)
}
if v := vmstorageDaily.GetIntMetric(t, "vm_daily_series_limit_rows_dropped_total"); v != 0 {
t.Fatalf("unexpected vm_daily_series_limit_rows_dropped_total value: %d", v)
}
vminsertDaily.PrometheusAPIV1ImportPrometheus(t, []string{
"foo_bar2 1 1652169600000", // 2022-05-10T08:00:00Z
}, apptest.QueryOpts{})
waitFor(
func() bool {
return vmstorageDaily.GetIntMetric(t, "vm_daily_series_limit_rows_dropped_total") > 0
},
)
// Test unlimited series
vmstorageUnlimited := tc.MustStartVmstorage("vmstorage-unlimited", []string{
"-storageDataPath=" + tc.Dir() + "/vmstorage-unlimited",
"-retentionPeriod=100y",
"-storage.maxHourlySeries=-1",
"-storage.maxDailySeries=-1",
})
vminsertUnlimited := tc.MustStartVminsert("vminsert-unlimited", []string{
"-storageNode=" + vmstorageUnlimited.VminsertAddr(),
})
metrics := make([]string, 0, 100)
for i := range 100 {
metrics = append(metrics, fmt.Sprintf("foo_bar%d 1 1652169600000", i)) // 2022-05-10T08:00:00Z
}
vminsertUnlimited.PrometheusAPIV1ImportPrometheus(t, metrics, apptest.QueryOpts{})
waitFor(
func() bool {
return vmstorageUnlimited.GetIntMetric(t, "vm_hourly_series_limit_current_series") > 0
},
)
if v := vmstorageUnlimited.GetIntMetric(t, "vm_hourly_series_limit_max_series"); v == 0 {
t.Fatalf("unexpected vm_hourly_series_limit_max_series value: %d", v)
}
if v := vmstorageUnlimited.GetIntMetric(t, "vm_hourly_series_limit_current_series"); v != 100 {
t.Fatalf("unexpected vm_hourly_series_limit_current_series value: %d", v)
}
if v := vmstorageUnlimited.GetIntMetric(t, "vm_hourly_series_limit_rows_dropped_total"); v != 0 {
t.Fatalf("unexpected vm_hourly_series_limit_rows_dropped_total value: %d", v)
}
if v := vmstorageUnlimited.GetIntMetric(t, "vm_daily_series_limit_max_series"); v == 0 {
t.Fatalf("unexpected vm_daily_series_limit_max_series value: %d", v)
}
if v := vmstorageUnlimited.GetIntMetric(t, "vm_daily_series_limit_current_series"); v != 100 {
t.Fatalf("unexpected vm_daily_series_limit_current_series value: %d", v)
}
if v := vmstorageUnlimited.GetIntMetric(t, "vm_daily_series_limit_rows_dropped_total"); v != 0 {
t.Fatalf("unexpected vm_daily_series_limit_rows_dropped_total value: %d", v)
}
}

View File

@@ -3,6 +3,7 @@ package tests
import (
"fmt"
"io"
"math"
"net/http"
"net/http/httptest"
"strings"
@@ -362,3 +363,147 @@ func TestSingleVMAgentDropOnOverload(t *testing.T) {
},
)
}
func TestSingleVMAgentCardinalityLimiter(t *testing.T) {
waitFor := func(f func() bool) {
const (
retries = 20
period = 100 * time.Millisecond
)
t.Helper()
for i := 0; i < retries; i++ {
if f() {
return
}
time.Sleep(period)
}
t.Fatalf("timed out waiting for retry #%d", retries)
}
tc := apptest.NewTestCase(t)
defer tc.Stop()
remoteWriteSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
defer remoteWriteSrv.Close()
// Verify hourly limit is applied
vmagent := tc.MustStartVmagent("vmagent-hourly", []string{
`-remoteWrite.flushInterval=50ms`,
fmt.Sprintf(`-remoteWrite.url=%s/api/v1/write`, remoteWriteSrv.URL),
"-remoteWrite.maxRowsPerBlock=1",
"-remoteWrite.maxHourlySeries=1",
"-remoteWrite.tmpDataPath=" + tc.Dir() + "/vmagent-hourly",
}, ``)
vmagent.APIV1ImportPrometheus(t, []string{
"foo_bar 1 1652169600000", // 2022-05-10T08:00:00Z
}, apptest.QueryOpts{})
if v := vmagent.GetIntMetric(t, "vmagent_hourly_series_limit_max_series"); v != 1 {
t.Fatalf("unexpected vmagent_hourly_series_limit_max_series value: %d", v)
}
if v := vmagent.GetIntMetric(t, "vmagent_hourly_series_limit_current_series"); v != 1 {
t.Fatalf("unexpected vmagent_hourly_series_limit_current_series value: %d", v)
}
if v := vmagent.GetIntMetric(t, "vmagent_hourly_series_limit_rows_dropped_total"); v != 0 {
t.Fatalf("unexpected vmagent_hourly_series_limit_rows_dropped_total value: %d", v)
}
vmagent.APIV1ImportPrometheusNoWaitFlush(t, []string{
"foo_bar2 1 1652169600000", // 2022-05-10T08:00:00Z
}, apptest.QueryOpts{})
waitFor(
func() bool {
return vmagent.GetIntMetric(t, "vmagent_hourly_series_limit_rows_dropped_total") > 0
},
)
// Daily limits
vmagent2 := tc.MustStartVmagent("vmagent-daily", []string{
`-remoteWrite.flushInterval=50ms`,
fmt.Sprintf(`-remoteWrite.url=%s/api/v1/write`, remoteWriteSrv.URL),
"-remoteWrite.maxRowsPerBlock=1",
"-remoteWrite.maxDailySeries=1",
"-remoteWrite.tmpDataPath=" + tc.Dir() + "/vmagent-daily",
}, ``)
vmagent2.APIV1ImportPrometheus(t, []string{
"foo_bar 1 1652169600000", // 2022-05-10T08:00:00Z
}, apptest.QueryOpts{})
if v := vmagent2.GetIntMetric(t, "vmagent_daily_series_limit_max_series"); v != 1 {
t.Fatalf("unexpected vmagent_daily_series_limit_max_series value: %d", v)
}
if v := vmagent2.GetIntMetric(t, "vmagent_daily_series_limit_current_series"); v != 1 {
t.Fatalf("unexpected vmagent_daily_series_limit_current_series value: %d", v)
}
if v := vmagent2.GetIntMetric(t, "vmagent_daily_series_limit_rows_dropped_total"); v != 0 {
t.Fatalf("unexpected vmagent_daily_series_limit_rows_dropped_total value: %d", v)
}
vmagent2.APIV1ImportPrometheusNoWaitFlush(t, []string{
"foo_bar2 1 1652169600000", // 2022-05-10T08:00:00Z
}, apptest.QueryOpts{})
waitFor(
func() bool {
return vmagent2.GetIntMetric(t, "vmagent_daily_series_limit_rows_dropped_total") > 0
},
)
// test running with unlimited tracker
vmagent3 := tc.MustStartVmagent("vmagent-unlimited", []string{
`-remoteWrite.flushInterval=50ms`,
fmt.Sprintf(`-remoteWrite.url=%s/api/v1/write`, remoteWriteSrv.URL),
"-remoteWrite.maxRowsPerBlock=10",
"-remoteWrite.maxDailySeries=-1",
"-remoteWrite.maxHourlySeries=-1",
"-remoteWrite.tmpDataPath=" + tc.Dir() + "/vmagent-unlimited",
}, ``)
metrics := make([]string, 0, 100)
for i := range 100 {
metrics = append(metrics, fmt.Sprintf("foo_bar%d 1 1652169600000", i)) // 2022-05-10T08:00:00Z
}
vmagent3.APIV1ImportPrometheusNoWaitFlush(t, metrics, apptest.QueryOpts{})
waitFor(
func() bool {
return vmagent3.GetIntMetric(t, "vmagent_hourly_series_limit_current_series") > 0
},
)
if v := vmagent3.GetIntMetric(t, "vmagent_hourly_series_limit_max_series"); v != math.MaxInt32 {
t.Fatalf("unexpected vmagent_hourly_series_limit_max_series value: %d", v)
}
if v := vmagent3.GetIntMetric(t, "vmagent_hourly_series_limit_current_series"); v != 100 {
t.Fatalf("unexpected vmagent_hourly_series_limit_current_series value: %d", v)
}
if v := vmagent3.GetIntMetric(t, "vmagent_hourly_series_limit_rows_dropped_total"); v != 0 {
t.Fatalf("unexpected vmagent_hourly_series_limit_rows_dropped_total value: %d", v)
}
if v := vmagent3.GetIntMetric(t, "vmagent_daily_series_limit_max_series"); v != math.MaxInt32 {
t.Fatalf("unexpected vmagent_daily_series_limit_max_series value: %d", v)
}
if v := vmagent3.GetIntMetric(t, "vmagent_daily_series_limit_current_series"); v != 100 {
t.Fatalf("unexpected vmagent_daily_series_limit_current_series value: %d", v)
}
if v := vmagent3.GetIntMetric(t, "vmagent_daily_series_limit_rows_dropped_total"); v != 0 {
t.Fatalf("unexpected vmagent_daily_series_limit_rows_dropped_total value: %d", v)
}
}

View File

@@ -556,6 +556,9 @@ See more details on [how to monitor VictoriaMetrics components](https://docs.vic
- `-storage.maxHourlySeries` is the limit on the number of [active time series](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-an-active-time-series) during the last hour.
- `-storage.maxDailySeries` is the limit on the number of unique time series during the day. This limit can be used for limiting daily [time series churn rate](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-high-churn-rate).
It is possible to use `-1` as a value for these flags{{% available_from "#" %}} in order to enable series tracking but set limit to maximum possible value.
This is useful in order to estimate the number of unique series written to `vmstorage` without enforcing limits.
Note that these limits are set and applied individually per each `vmstorage` node in the cluster. So, if the cluster has `N` `vmstorage` nodes, then the cluster-level limits will be `N` times bigger than the per-`vmstorage` limits.
See more details about cardinality limiter in [these docs](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#cardinality-limiter).

View File

@@ -1976,6 +1976,9 @@ By default, VictoriaMetrics doesn't limit the number of stored time series. The
Both limits can be set simultaneously. If any of these limits is reached, then incoming samples for new time series are dropped. A sample of dropped series is put in the log with `WARNING` level.
It is possible to use `-1` as a value for these flags{{% available_from "#" %}} in order to enable series tracking but set limit to maximum possible value.
This is useful in order to estimate the number of unique series which is written to VictoriaMetrics single without enforcing limits.
The exceeded limits can be [monitored](#monitoring) with the following metrics:
* `vm_hourly_series_limit_rows_dropped_total` - the number of metrics dropped due to exceeded hourly limit on the number of unique time series.

View File

@@ -34,12 +34,12 @@ See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-rel
* FEATURE: [dashboards/alert-statistics](https://grafana.com/grafana/dashboards/24553): add pending and firing alerts stats; fix query in `FIRING over time by group` panel. See [#10571](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10571). Thanks to @sias32 for the contribution.
* FEATURE: [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/): add random jitter to concurrent periodical flushers targeting the remote write destination. This helps spread remote write flushes across the flush interval, avoiding congestion at the remote write destination and enhancing queue data consumption. See [#10729](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10729).
* FEATURE: [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/): expose `vmalert_remotewrite_sent_rows` and `vmalert_remotewrite_sent_bytes` histograms to provide better visibility into remote write request sizes. See [#10727](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10727).
* FEATURE: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/): allow setting `-1` value for `-remoteWrite.maxHourlySeries` and `-remoteWrite.maxDailySeries` command-line flags. This automatically sets limits to the highest possible value in order to enable tracking without enforcing any limits. This is helpful for estimating current usage before applying real limits. See [#9614](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/9614).
* FEATURE: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): allow setting `-1` value for `-storage.maxHourlySeries` and `-storage.maxDailySeries` command-line flags. This automatically sets limits to the highest possible value in order to enable tracking without enforcing any limits. This is helpful for estimating current usage before applying real limits. See [#9614](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/9614).
* BUGFIX: [vmbackup](https://docs.victoriametrics.com/vmbackup/), [vmbackupmanager](https://docs.victoriametrics.com/victoriametrics/vmbackupmanager/): retry the requests that failed with unexpected EOF due to unstable network to S3 service. See [#10699](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10699).
* BUGFIX: All VictoriaMetrics components: Fix an issue where `unsupported` metric metadata type was exposed for summaries and quantiles if a summary wasn't updated within a certain time window. See [metrics#120](https://github.com/VictoriaMetrics/metrics/issues/120) and [metrics#121](https://github.com/VictoriaMetrics/metrics/pull/121).
* BUGFIX: [vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/): align request body buffering flags - `maxRequestBodySizeToRetry` and `requestBufferSize` to the same `16KB` value. Allow disabling request buffering by setting `requestBufferSize=0`. See [#10675](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10675)
* BUGFIX: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/): fix `scrape_series_added` metric to update only on successful scrapes, aligning its behavior with Prometheus. See [#10653](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10653).
## [v1.139.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.139.0)

View File

@@ -941,6 +941,9 @@ The limit can be enforced by setting the following command-line flags:
* `-remoteWrite.maxDailySeries` - limits the number of unique time series `vmagent` can write to remote storage systems during the last day.
Useful for limiting daily churn rate.
It is possible to use `-1` as a value for these flags{{% available_from "#" %}} in order to enable series tracking but set limit to maximum possible value.
This is useful in order to estimate the number of unique series which is written to remote storage systems without enforcing limits.
Both limits can be set simultaneously. If any of these limits is reached, then samples for new time series are dropped instead of sending
them to remote storage systems. A sample of dropped series is put in the log with `WARNING` level.