Compare commits

..

7 Commits

Author SHA1 Message Date
Stephan Burns
331389f4e1 Remove ID fields 2026-08-13 16:00:40 -04:00
Dhruvan Tanna
1b242a8c71 app/vmselect: scale default concurrency with available CPUs (#11205)
Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11191

The default `-search.maxConcurrentRequests` for cluster `vmselect` was capped at 16 via `min(2*cgroup.AvailableCPUs(), 16)`. This meant larger vmselect nodes kept the same default concurrency once they had more than 8 CPUs.

This changes the default to `2*cgroup.AvailableCPUs()`, matching the style already used by `vmstorage` and
`clusternative.maxConcurrentRequests`.

Capping it at 16 makes sense for vmsingle, since it handles both ingestion and querying, with ingestion being the higher priority. Select has no such limitation and should scale to all available resources.

---------

Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-08-13 17:08:00 +03:00
JAYICE
c1f3589248 app/vmalert: properly update eval_delay and eval_alignment on config reload (#11380)
Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11374

Signed-off-by: “Jayice” <jzhou@victoriametrics.com>
Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-08-13 16:52:34 +03:00
Max Kotliar
389ad7933e docs/changelog: follow-up on prev commit 1a1c61083d 2026-08-13 16:23:27 +03:00
Kirill Yurkov
1a1c61083d app/vmalert: rename vmalert_rule_group_results_limit back to vmalert_group_rule_results_limit (#11375)
rename `vmalert_rule_group_results_limit` back to
`vmalert_group_rule_results_limit`. The metric was introduced in
[v1.147.0](https://docs.victoriametrics.com/victoriametrics/changelog/#v11470)
but was accidentally given the wrong name. Restoring the right metric
name.

---------

Co-authored-by: Hui Wang <haley@victoriametrics.com>
Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-08-13 16:14:48 +03:00
Artem Fetishev
8b59f970f3 lib/storage: follow-up for 15a21d9791 (#11398)
Restore checking globalIndexTimeRange instead of db.s.disableGlobalIndex since
currently indexDB has no control on which time range it receives from storage
and it receives globalIndexTimeRange.

See https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11398#discussion_r3774317249

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-08-13 14:38:25 +02:00
Artem Fetishev
15a21d9791 lib/storage: check early whether an indexDB contains the time range (#11398)
Each indexDB public method that accepts a time range or date now
performs this check explicitly and the very beginning. This check
existed before, but was not obvious and performed in the middle of the
search request. Performing this check early allows to avoid any
unnecessary computations an sometimes even index searches.

Related to #11196.

---------

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-08-13 13:46:34 +02:00
21 changed files with 234 additions and 160 deletions

View File

@@ -290,6 +290,8 @@ func (g *Group) updateWith(newGroup *Group) error {
g.Headers = newGroup.Headers
g.NotifierHeaders = newGroup.NotifierHeaders
g.Labels = newGroup.Labels
g.EvalDelay = newGroup.EvalDelay
g.evalAlignment = newGroup.evalAlignment
g.Limit = newGroup.Limit
g.checksum = newGroup.checksum
g.Rules = newRules
@@ -337,7 +339,7 @@ func (g *Group) Init() {
i := g.Interval.Seconds()
return i
})
g.metrics.iterationLimit = g.metrics.set.NewGauge(fmt.Sprintf(`vmalert_rule_group_results_limit{%s}`, labels), func() float64 {
g.metrics.iterationLimit = g.metrics.set.NewGauge(fmt.Sprintf(`vmalert_group_rule_results_limit{%s}`, labels), func() float64 {
g.mu.RLock()
limit := g.Limit
g.mu.RUnlock()

View File

@@ -78,6 +78,12 @@ func TestUpdateWith(t *testing.T) {
if g.Debug != expect.Debug {
t.Fatalf("expected to have debug %v; got %v", expect.Debug, g.Debug)
}
if !durationPtrEqual(g.EvalDelay, expect.EvalDelay) {
t.Fatalf("expected to have eval_delay %v; got %v", expect.EvalDelay, g.EvalDelay)
}
if !boolPtrEqual(g.evalAlignment, expect.evalAlignment) {
t.Fatalf("expected to have eval_alignment %v; got %v", expect.evalAlignment, g.evalAlignment)
}
}
// new rule
@@ -237,6 +243,37 @@ func TestUpdateWith(t *testing.T) {
{Alert: "foo1", Debug: &debug},
},
})
// update group evaluation settings
evalDelay := promutil.NewDuration(time.Minute)
evalAlignment := false
f(config.Group{
Rules: []config.Rule{{
Record: "foo",
Expr: "max(up)",
}},
}, config.Group{
EvalDelay: evalDelay,
EvalAlignment: &evalAlignment,
Rules: []config.Rule{{
Record: "foo",
Expr: "min(up)",
}},
})
}
func durationPtrEqual(a, b *time.Duration) bool {
if a == nil || b == nil {
return a == b
}
return *a == *b
}
func boolPtrEqual(a, b *bool) bool {
if a == nil || b == nil {
return a == b
}
return *a == *b
}
func TestUpdateDuringRandSleep(t *testing.T) {

View File

@@ -59,19 +59,6 @@
},
"type": "dashboard"
},
{
"datasource": {
"type": "prometheus",
"uid": "$ds"
},
"enable": true,
"expr": "sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n) \nunless \n(\n sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n ) offset $__interval\n)",
"hide": true,
"iconColor": "dark-blue",
"name": "version",
"textFormat": "{{version}}",
"titleFormat": "Version change"
},
{
"datasource": {
"type": "prometheus",

View File

@@ -25,7 +25,6 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": 3,
"links": [
{
"icon": "doc",

View File

@@ -37,7 +37,7 @@
"uid": "$ds"
},
"enable": true,
"expr": "sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n) \nunless \n(\n sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n ) offset $__interval\n)",
"expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset $__interval) by(version))",
"hide": true,
"iconColor": "dark-blue",
"name": "version change",

View File

@@ -37,7 +37,7 @@
"uid": "$ds"
},
"enable": true,
"expr": "sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n) \nunless \n(\n sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n ) offset $__interval\n)",
"expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset $__interval) by(version))",
"hide": true,
"iconColor": "dark-blue",
"name": "version",
@@ -62,7 +62,6 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": 13,
"links": [
{
"icon": "doc",

View File

@@ -60,19 +60,6 @@
},
"type": "dashboard"
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"enable": true,
"expr": "sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n) \nunless \n(\n sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n ) offset $__interval\n)",
"hide": true,
"iconColor": "dark-blue",
"name": "version",
"textFormat": "{{version}}",
"titleFormat": "Version change"
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",

View File

@@ -38,7 +38,7 @@
"uid": "$ds"
},
"enable": true,
"expr": "sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n) \nunless \n(\n sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n ) offset $__interval\n)",
"expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset $__interval) by(version))",
"hide": true,
"iconColor": "dark-blue",
"name": "version change",

View File

@@ -38,7 +38,7 @@
"uid": "$ds"
},
"enable": true,
"expr": "sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n) \nunless \n(\n sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n ) offset $__interval\n)",
"expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset $__interval) by(version))",
"hide": true,
"iconColor": "dark-blue",
"name": "version",

View File

@@ -26,11 +26,11 @@
"uid": "$ds"
},
"enable": true,
"expr": "sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n) \nunless \n(\n sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n ) offset $__interval\n)",
"expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(short_version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset $__interval) by(short_version))",
"hide": true,
"iconColor": "dark-blue",
"name": "version",
"textFormat": "{{version}}",
"textFormat": "{{short_version}}",
"titleFormat": "Version change"
},
{

View File

@@ -26,11 +26,11 @@
"uid": "$ds"
},
"enable": true,
"expr": "sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n) \nunless \n(\n sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n ) offset $__interval\n)",
"expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(short_version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset $__interval) by(short_version))",
"hide": true,
"iconColor": "dark-blue",
"name": "version",
"textFormat": "{{version}}",
"textFormat": "{{short_version}}",
"titleFormat": "Version change"
},
{

View File

@@ -54,19 +54,6 @@
},
"type": "dashboard"
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"enable": true,
"expr": "sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n) \nunless \n(\n sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n ) offset $__interval\n)",
"hide": true,
"iconColor": "dark-blue",
"name": "version",
"textFormat": "{{version}}",
"titleFormat": "Version change"
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",

View File

@@ -25,11 +25,11 @@
"uid": "$ds"
},
"enable": true,
"expr": "sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n) \nunless \n(\n sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n ) offset $__interval\n)",
"expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(short_version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset $__interval) by(short_version))",
"hide": true,
"iconColor": "dark-blue",
"name": "version",
"textFormat": "{{version}}",
"textFormat": "{{short_version}}",
"titleFormat": "Version change"
},
{
@@ -50,7 +50,6 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": 3,
"links": [
{
"icon": "doc",

View File

@@ -25,11 +25,11 @@
"uid": "$ds"
},
"enable": true,
"expr": "sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n) \nunless \n(\n sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n ) offset $__interval\n)",
"expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(short_version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset $__interval) by(short_version))",
"hide": true,
"iconColor": "dark-blue",
"name": "version",
"textFormat": "{{version}}",
"textFormat": "{{short_version}}",
"titleFormat": "Version change"
},
{
@@ -50,7 +50,6 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": 3,
"links": [
{
"icon": "doc",

View File

@@ -53,19 +53,6 @@
},
"type": "dashboard"
},
{
"datasource": {
"type": "prometheus",
"uid": "$ds"
},
"enable": true,
"expr": "sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n) \nunless \n(\n sum by(version) (\n label_replace(vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version!=\"\"}, \"version\", \"$1\", \"short_version\", \"(.*)\")\n OR\n vm_app_version{job=~\"$job\", instance=~\"$instance\", short_version=\"\"}\n ) offset $__interval\n)",
"hide": true,
"iconColor": "dark-blue",
"name": "version",
"textFormat": "{{version}}",
"titleFormat": "Version change"
},
{
"datasource": {
"type": "prometheus",

View File

@@ -38,6 +38,9 @@ See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-rel
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): change the HTTP response code for [Prometheus querying API](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#prometheus-querying-api-usage) requests from `422 Unprocessable Entity` to `400 Bad Request` when request parameters are missing or incorrect. See [#11330](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11330).
* BUGFIX: [vmui](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui): respect the custom query step specified via `g0.step_input` when opening a URL. Previously, it could be reset to the automatically calculated step and potentially cause dashboards to freeze. See [#11137](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11137).
* BUGFIX: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/) and [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/): properly assign scrape target IP address at IPv6-only networks for [docker_sd_configs](https://docs.victoriametrics.com/victoriametrics/sd_configs/#docker_sd_configs). See [#10965](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10965).
* BUGFIX: [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/): rename `vmalert_rule_group_results_limit` back to `vmalert_group_rule_results_limit`. The metric was introduced in [v1.147.0](https://docs.victoriametrics.com/victoriametrics/changelog/#v11470) but was accidentally given the wrong name. See [#11179](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11179).
* BUGFIX: [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/): properly update group-level `eval_delay` and `eval_alignment` for existing groups during runtime when config reload is triggered periodically or manually via `/-/reload`. Previously, these settings weren't updated after config reload during runtime. See [#11374](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11374).
* BUGFIX: `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): scale the default `-search.maxConcurrentRequests` with the number of available CPU cores instead of capping it at 16. See [#11191](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11191). Thanks to @Dhru1Tanna for contribution.
## [v1.149.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.149.0)

View File

@@ -99,7 +99,7 @@ type indexDB struct {
// legacy indexDBs, since these indexDBs are readonly.
// This field cannot be used for the partition indexDBs, since they may receive data
// with bigger timestamps at any time.
legacyMinMissingTimestampByKey map[string]int64
legacyMinMissingTimestampByKey map[TenantToken]int64
// protects legacyMinMissingTimestampByKey
legacyMinMissingTimestampByKeyLock sync.Mutex
@@ -174,7 +174,7 @@ func mustOpenIndexDB(id uint64, tr TimeRange, name, path string, s *Storage, isR
tfssCache := lrucache.NewCache(getTagFiltersCacheSize)
tb := mergeset.MustOpenTable(path, dataFlushInterval, tfssCache.Reset, 0, mergeTagToMetricIDsRows, isReadOnly)
db := &indexDB{
legacyMinMissingTimestampByKey: make(map[string]int64),
legacyMinMissingTimestampByKey: make(map[TenantToken]int64),
id: id,
tr: tr,
name: name,
@@ -508,6 +508,11 @@ func (db *indexDB) SearchLabelNames(qt *querytracer.Tracer, tfss []*TagFilters,
qt = qt.NewChild("search label names: filters=%s, timeRange=%s, maxLabelNames=%d, maxMetrics=%d", tfss, &tr, maxLabelNames, maxMetrics)
defer qt.Done()
if !db.legacyContainsTimeRange(tr) {
qt.Printf("indexDB doesn't contain data for the given time range: %v", &tr)
return nil, nil
}
is := db.getIndexSearch(deadline)
lns, err := is.searchLabelNamesWithFiltersOnTimeRange(qt, tfss, tr, maxLabelNames, maxMetrics)
db.putIndexSearch(is)
@@ -710,6 +715,11 @@ func (db *indexDB) SearchLabelValues(qt *querytracer.Tracer, labelName string, t
qt = qt.NewChild("search label values: labelName=%q, filters=%s, timeRange=%s, maxLabelValues=%d, maxMetrics=%d", labelName, tfss, &tr, maxLabelValues, maxMetrics)
defer qt.Done()
if !db.legacyContainsTimeRange(tr) {
qt.Printf("indexDB doesn't contain data for the given time range: %v", &tr)
return nil, nil
}
key := labelName
if key == "__name__" {
key = ""
@@ -958,6 +968,11 @@ func (db *indexDB) SearchTagValueSuffixes(qt *querytracer.Tracer, tr TimeRange,
&tr, tagKey, tagValuePrefix, delimiter, maxTagValueSuffixes)
defer qt.Done()
if !db.legacyContainsTimeRange(tr) {
qt.Printf("indexDB doesn't contain data for the given time range: %v", &tr)
return nil, nil
}
// TODO: cache results?
is := db.getIndexSearch(deadline)
@@ -1092,6 +1107,11 @@ func (db *indexDB) SearchGraphitePaths(qt *querytracer.Tracer, tr TimeRange, qHe
qt = qt.NewChild("search graphite paths: timeRange=%s, qHead=%q, qTail=%q, maxPaths=%d", &tr, bytesutil.ToUnsafeString(qHead), bytesutil.ToUnsafeString(qTail), maxPaths)
defer qt.Done()
if !db.legacyContainsTimeRange(tr) {
qt.Printf("indexDB doesn't contain data for the given time range: %v", &tr)
return nil, nil
}
n := bytes.IndexAny(qTail, "*[{")
if n < 0 {
// Verify that qHead matches a metric name.
@@ -1273,6 +1293,11 @@ func (db *indexDB) GetTSDBStatus(qt *querytracer.Tracer, tfss []*TagFilters, dat
qt = qt.NewChild("collect TSDB status: filters=%s, date=%s, focusLabel=%q, topN=%d, maxMetrics=%d", tfss, dateToString(date), focusLabel, topN, maxMetrics)
defer qt.Done()
if !db.legacyContainsDate(date) {
qt.Printf("indexDB doesn't contain data for the given date: %s", dateToString(date))
return &TSDBStatus{}, nil
}
is := db.getIndexSearch(deadline)
defer db.putIndexSearch(is)
status, err := is.getTSDBStatus(qt, tfss, date, focusLabel, topN, maxMetrics)
@@ -1720,6 +1745,11 @@ func (db *indexDB) SearchTSIDs(qt *querytracer.Tracer, tfss []*TagFilters, tr Ti
qt = qt.NewChild("search TSIDs: filters=%s, timeRange=%s, maxMetrics=%d", tfss, &tr, maxMetrics)
defer qt.Done()
if !db.legacyContainsTimeRange(tr) {
qt.Printf("indexDB doesn't contain data for the given time range: %v", &tr)
return nil, nil
}
metricIDs, err := db.searchMetricIDs(qt, tfss, tr, maxMetrics, deadline)
if err != nil {
return nil, db.wrapError("search TSIDs", err)
@@ -1803,6 +1833,11 @@ func (db *indexDB) SearchMetricNames(qt *querytracer.Tracer, tfss []*TagFilters,
qt = qt.NewChild("search metric names: filters=%s, timeRange=%s, maxMetrics=%d", tfss, &tr, maxMetrics)
defer qt.Done()
if !db.legacyContainsTimeRange(tr) {
qt.Printf("indexDB doesn't contain data for the given time range: %v", &tr)
return nil, nil
}
metricIDs, err := db.searchMetricIDs(qt, tfss, tr, maxMetrics, deadline)
if err != nil {
return nil, db.wrapError("search metric names", err)
@@ -2219,18 +2254,12 @@ func (is *indexSearch) searchMetricIDsInternal(qt *querytracer.Tracer, tfss []*T
qt = qt.NewChild("search for metric ids: filters=%s, timeRange=%s, maxMetrics=%d", tfss, &tr, maxMetrics)
defer qt.Done()
metricIDs := &uint64set.Set{}
if !is.legacyContainsTimeRange(tr) {
qt.Printf("indexdb doesn't contain data for the given timeRange=%s", &tr)
return metricIDs, nil
}
if tr.MinTimestamp >= is.db.s.minTimestampForCompositeIndex {
tfss = convertToCompositeTagFilterss(tfss)
qt.Printf("composite filters=%s", tfss)
}
metricIDs := &uint64set.Set{}
for _, tfs := range tfss {
if len(tfs.tfs) == 0 {
// An empty filters must be equivalent to `{__name__!=""}`

View File

@@ -7,7 +7,6 @@ import (
"strconv"
"sync/atomic"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
@@ -87,48 +86,61 @@ func mustOpenLegacyIndexDB(path string, s *Storage) *legacyIndexDB {
return legacyIDB
}
func (is *indexSearch) legacyContainsTimeRange(tr TimeRange) bool {
func (db *indexDB) legacyContainsDate(date uint64) bool {
var tr TimeRange
if date == globalIndexDate {
tr = globalIndexTimeRange
} else {
tr.MinTimestamp = int64(date) * msecPerDay
tr.MaxTimestamp = int64(date+1)*msecPerDay - 1
}
return db.legacyContainsTimeRange(tr)
}
func (db *indexDB) legacyContainsTimeRange(tr TimeRange) bool {
if tr == globalIndexTimeRange {
return true
}
db := is.db
if !db.noRegisterNewSeries.Load() {
// indexDB could register new time series - it is not safe to cache minMissingTimestamp
return true
}
// use common prefix as a key for minMissingTimestamp
// it's needed to properly track timestamps for cluster version
// which uses tenant labels for the index search
kb := &is.kb
kb.B = is.marshalCommonPrefix(kb.B[:0], nsPrefixDateToMetricID)
key := kb.B
// vmsingle does not have tenants and therefore has just one key.
// While vmstorage can potentially have many tenants and the actual
// accountID and projectID will be set from the request.
key := TenantToken{
AccountID: 0,
ProjectID: 0,
}
db.legacyMinMissingTimestampByKeyLock.Lock()
minMissingTimestamp, ok := db.legacyMinMissingTimestampByKey[string(key)]
minMissingTimestamp, ok := db.legacyMinMissingTimestampByKey[key]
db.legacyMinMissingTimestampByKeyLock.Unlock()
if ok && tr.MinTimestamp >= minMissingTimestamp {
// Fast path.
return false
}
if is.legacyContainsTimeRangeSlow(kb, tr) {
// Slow path.
is := db.getIndexSearch(noDeadline)
defer db.putIndexSearch(is)
if is.legacyContainsTimeRange(tr) {
return true
}
db.legacyMinMissingTimestampByKeyLock.Lock()
minMissingTimestamp, ok = db.legacyMinMissingTimestampByKey[string(key)]
minMissingTimestamp, ok = db.legacyMinMissingTimestampByKey[key]
if !ok || tr.MinTimestamp < minMissingTimestamp {
db.legacyMinMissingTimestampByKey[string(key)] = tr.MinTimestamp
db.legacyMinMissingTimestampByKey[key] = tr.MinTimestamp
}
db.legacyMinMissingTimestampByKeyLock.Unlock()
return false
}
func (is *indexSearch) legacyContainsTimeRangeSlow(prefixBuf *bytesutil.ByteBuffer, tr TimeRange) bool {
ts := &is.ts
func (is *indexSearch) legacyContainsTimeRange(tr TimeRange) bool {
// Verify whether the tr.MinTimestamp is included into `ts` or is smaller than the minimum date stored in `ts`.
// Do not check whether tr.MaxTimestamp is included into `ts` or is bigger than the max date stored in `ts` for performance reasons.
// This means that this func can return true if `tr` is located below the min date stored in `ts`.
@@ -136,12 +148,17 @@ func (is *indexSearch) legacyContainsTimeRangeSlow(prefixBuf *bytesutil.ByteBuff
// The main practical case allows skipping searching in prev indexdb (`ts`) when `tr`
// is located above the max date stored there.
minDate := uint64(tr.MinTimestamp) / msecPerDay
prefix := prefixBuf.B
prefixBuf.B = encoding.MarshalUint64(prefixBuf.B, minDate)
ts.Seek(prefixBuf.B)
kb := &is.kb
kb.B = is.marshalCommonPrefix(kb.B[:0], nsPrefixDateToMetricID)
prefix := kb.B
kb.B = encoding.MarshalUint64(kb.B, minDate)
ts := &is.ts
ts.Seek(kb.B)
if !ts.NextItem() {
if err := ts.Error(); err != nil {
logger.Panicf("FATAL: error when searching for minDate=%d, prefix %q: %s", minDate, prefixBuf.B, err)
logger.Panicf("FATAL: error when searching for minDate=%d, prefix %q: %s", minDate, kb.B, err)
}
return false
}

View File

@@ -29,11 +29,7 @@ func TestLegacyContainsTimeRange(t *testing.T) {
f := func(idb *indexDB, tr TimeRange, want bool) {
t.Helper()
is := idb.getIndexSearch(noDeadline)
defer idb.putIndexSearch(is)
got := is.legacyContainsTimeRange(tr)
got := idb.legacyContainsTimeRange(tr)
if got != want {
t.Fatalf("legacyContainsTimeRange(%s) for index db %s returns unexpected result: got %t, want %t", tr.String(), idb.name, got, want)
}
@@ -97,8 +93,8 @@ func TestLegacyContainsTimeRange(t *testing.T) {
f(legacyIDBs.getIDBCurr(), tr, true)
f(idb, tr, true)
// Fully inside trPt, overlaps with trPrev on the right side and trCurr on
// the left side.
// Fully inside trPt, overlaps with trPrev on the right side and with trCurr
// on the left side.
tr = TimeRange{
MinTimestamp: time.Date(2025, 1, 7, 0, 0, 0, 0, time.UTC).UnixMilli(),
MaxTimestamp: time.Date(2025, 1, 21, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),

View File

@@ -2241,15 +2241,16 @@ func TestIndexSearchLegacyContainsTimeRange_Concurrent(t *testing.T) {
for i := range concurrency {
ts := minTimestamp + msecPerDay*i
wg.Go(func() {
is := idb.getIndexSearch(noDeadline)
_ = is.legacyContainsTimeRange(TimeRange{ts, ts})
idb.putIndexSearch(is)
_ = idb.legacyContainsTimeRange(TimeRange{ts, ts})
})
}
wg.Wait()
key := marshalCommonPrefix(nil, nsPrefixDateToMetricID)
if got, want := idb.legacyMinMissingTimestampByKey[string(key)], minTimestamp; got != want {
key := TenantToken{
AccountID: 0,
ProjectID: 0,
}
if got, want := idb.legacyMinMissingTimestampByKey[key], minTimestamp; got != want {
t.Fatalf("unexpected min timestamp: got %v, want %v", time.UnixMilli(got).UTC(), time.UnixMilli(want).UTC())
}
}

View File

@@ -33,16 +33,22 @@ func TestLegacyStorage_SearchMetricNames(t *testing.T) {
return mrs, want
}
const numMetrics = 1000
tr := TimeRange{
tr1 := TimeRange{
MinTimestamp: time.Date(2023, 6, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
MaxTimestamp: time.Date(2024, 5, 31, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
}
legacyData, wantLegacy := genData(numMetrics, "legacy", tr)
newData, wantNew := genData(numMetrics, "new", tr)
wantNew = append(wantNew, wantLegacy...)
slices.Sort(wantNew)
tr2 := TimeRange{
MinTimestamp: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
MaxTimestamp: time.Date(2024, 6, 30, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
}
legacyData, wantLegacy := genData(numMetrics, "legacy", tr1)
new1Data, wantNew1 := genData(numMetrics, "new1", tr1)
new2Data, wantNew2 := genData(numMetrics, "new2", tr2)
newData := slices.Concat(new1Data, new2Data)
wantLegacyAndNew1 := slices.Concat(wantLegacy, wantNew1)
slices.Sort(wantLegacyAndNew1)
assertSearchResults := func(s *Storage, want []string) {
assertSearchResults := func(s *Storage, tr TimeRange, want []string) {
t.Helper()
tfsAll := NewTagFilters()
if err := tfsAll.Add([]byte("__name__"), []byte(".*"), false, true); err != nil {
@@ -67,10 +73,11 @@ func TestLegacyStorage_SearchMetricNames(t *testing.T) {
}
assertLegacyData := func(s *Storage) {
assertSearchResults(s, wantLegacy)
assertSearchResults(s, tr1, wantLegacy)
}
assertNewData := func(s *Storage) {
assertSearchResults(s, wantNew)
assertSearchResults(s, tr1, wantLegacyAndNew1)
assertSearchResults(s, tr2, wantNew2)
}
testSearchOpWithLegacyIndexDBs(t, legacyData, newData, assertLegacyData, assertNewData)
}
@@ -95,15 +102,22 @@ func TestLegacyStorage_SearchLabelNames(t *testing.T) {
return mrs, want
}
const numMetrics = 1000
tr := TimeRange{
tr1 := TimeRange{
MinTimestamp: time.Date(2023, 6, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
MaxTimestamp: time.Date(2024, 5, 31, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
}
legacyData, wantLegacy := genData(numMetrics, "legacy", tr)
newData, wantNew := genData(numMetrics, "new", tr)
wantNew = append(wantNew, wantLegacy...)
tr2 := TimeRange{
MinTimestamp: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
MaxTimestamp: time.Date(2024, 6, 30, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
}
legacyData, wantLegacy := genData(numMetrics, "legacy", tr1)
new1Data, wantNew1 := genData(numMetrics, "new1", tr1)
new2Data, wantNew2 := genData(numMetrics, "new2", tr2)
newData := slices.Concat(new1Data, new2Data)
wantLegacyAndNew1 := slices.Concat(wantLegacy, wantNew1)
slices.Sort(wantLegacyAndNew1)
assertSearchResults := func(s *Storage, want []string) {
assertSearchResults := func(s *Storage, tr TimeRange, want []string) {
t.Helper()
got, err := s.SearchLabelNames(nil, nil, tr, 1e9, 1e9, noDeadline)
if err != nil {
@@ -118,12 +132,16 @@ func TestLegacyStorage_SearchLabelNames(t *testing.T) {
assertLegacyData := func(s *Storage) {
want := append(wantLegacy, "__name__")
slices.Sort(want)
assertSearchResults(s, want)
assertSearchResults(s, tr1, want)
}
assertNewData := func(s *Storage) {
want := append(wantNew, "__name__")
want := append(wantLegacyAndNew1, "__name__")
slices.Sort(want)
assertSearchResults(s, want)
assertSearchResults(s, tr1, want)
want = append(wantNew2, "__name__")
slices.Sort(want)
assertSearchResults(s, tr2, want)
}
testSearchOpWithLegacyIndexDBs(t, legacyData, newData, assertLegacyData, assertNewData)
}
@@ -148,16 +166,22 @@ func TestLegacyStorage_SearchLabelValues(t *testing.T) {
return mrs, want
}
const numMetrics = 1000
tr := TimeRange{
tr1 := TimeRange{
MinTimestamp: time.Date(2023, 6, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
MaxTimestamp: time.Date(2024, 5, 31, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
}
legacyData, wantLegacy := genData(numMetrics, "legacy", tr)
newData, wantNew := genData(numMetrics, "new", tr)
wantNew = append(wantNew, wantLegacy...)
slices.Sort(wantNew)
tr2 := TimeRange{
MinTimestamp: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
MaxTimestamp: time.Date(2024, 6, 30, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
}
legacyData, wantLegacy := genData(numMetrics, "legacy", tr1)
new1Data, wantNew1 := genData(numMetrics, "new1", tr1)
new2Data, wantNew2 := genData(numMetrics, "new2", tr2)
newData := slices.Concat(new1Data, new2Data)
wantLegacyAndNew1 := slices.Concat(wantLegacy, wantNew1)
slices.Sort(wantLegacyAndNew1)
assertSearchResults := func(s *Storage, want []string) {
assertSearchResults := func(s *Storage, tr TimeRange, want []string) {
t.Helper()
got, err := s.SearchLabelValues(nil, "label", nil, tr, 1e9, 1e9, noDeadline)
if err != nil {
@@ -171,11 +195,12 @@ func TestLegacyStorage_SearchLabelValues(t *testing.T) {
assertLegacyData := func(s *Storage) {
t.Helper()
assertSearchResults(s, wantLegacy)
assertSearchResults(s, tr1, wantLegacy)
}
assertNewData := func(s *Storage) {
t.Helper()
assertSearchResults(s, wantNew)
assertSearchResults(s, tr1, wantLegacyAndNew1)
assertSearchResults(s, tr2, wantNew2)
}
testSearchOpWithLegacyIndexDBs(t, legacyData, newData, assertLegacyData, assertNewData)
}
@@ -197,16 +222,22 @@ func TestLegacyStorage_SearchTagValueSuffixes(t *testing.T) {
return mrs, want
}
const numMetrics = 1000
tr := TimeRange{
tr1 := TimeRange{
MinTimestamp: time.Date(2023, 6, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
MaxTimestamp: time.Date(2024, 5, 31, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
}
legacyData, wantLegacy := genData(numMetrics, "legacy", tr)
newData, wantNew := genData(numMetrics, "new", tr)
wantNew = append(wantNew, wantLegacy...)
slices.Sort(wantNew)
tr2 := TimeRange{
MinTimestamp: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
MaxTimestamp: time.Date(2024, 6, 30, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
}
legacyData, wantLegacy := genData(numMetrics, "legacy", tr1)
new1Data, wantNew1 := genData(numMetrics, "new1", tr1)
new2Data, wantNew2 := genData(numMetrics, "new2", tr2)
newData := slices.Concat(new1Data, new2Data)
wantLegacyAndNew1 := slices.Concat(wantLegacy, wantNew1)
slices.Sort(wantLegacyAndNew1)
assertSearchResults := func(s *Storage, want []string) {
assertSearchResults := func(s *Storage, tr TimeRange, want []string) {
t.Helper()
got, err := s.SearchTagValueSuffixes(nil, tr, "", "prefix.", '.', 1e9, noDeadline)
if err != nil {
@@ -221,11 +252,12 @@ func TestLegacyStorage_SearchTagValueSuffixes(t *testing.T) {
assertLegacyData := func(s *Storage) {
t.Helper()
assertSearchResults(s, wantLegacy)
assertSearchResults(s, tr1, wantLegacy)
}
assertNewData := func(s *Storage) {
t.Helper()
assertSearchResults(s, wantNew)
assertSearchResults(s, tr1, wantLegacyAndNew1)
assertSearchResults(s, tr2, wantNew2)
}
testSearchOpWithLegacyIndexDBs(t, legacyData, newData, assertLegacyData, assertNewData)
}
@@ -247,16 +279,22 @@ func TestLegacyStorage_SearchGraphitePaths(t *testing.T) {
return mrs, want
}
const numMetrics = 1000
tr := TimeRange{
tr1 := TimeRange{
MinTimestamp: time.Date(2023, 6, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
MaxTimestamp: time.Date(2024, 5, 31, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
}
legacyData, wantLegacy := genData(numMetrics, "legacy", tr)
newData, wantNew := genData(numMetrics, "new", tr)
wantNew = append(wantNew, wantLegacy...)
slices.Sort(wantNew)
tr2 := TimeRange{
MinTimestamp: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
MaxTimestamp: time.Date(2024, 6, 30, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
}
legacyData, wantLegacy := genData(numMetrics, "legacy", tr1)
new1Data, wantNew1 := genData(numMetrics, "new1", tr1)
new2Data, wantNew2 := genData(numMetrics, "new2", tr2)
newData := slices.Concat(new1Data, new2Data)
wantLegacyAndNew1 := slices.Concat(wantLegacy, wantNew1)
slices.Sort(wantLegacyAndNew1)
assertSearchResults := func(s *Storage, want []string) {
assertSearchResults := func(s *Storage, tr TimeRange, want []string) {
t.Helper()
got, err := s.SearchGraphitePaths(nil, tr, []byte("*.*"), 1e9, noDeadline)
if err != nil {
@@ -271,16 +309,17 @@ func TestLegacyStorage_SearchGraphitePaths(t *testing.T) {
assertLegacyData := func(s *Storage) {
t.Helper()
assertSearchResults(s, wantLegacy)
assertSearchResults(s, tr1, wantLegacy)
}
assertNewData := func(s *Storage) {
t.Helper()
assertSearchResults(s, wantNew)
assertSearchResults(s, tr1, wantLegacyAndNew1)
assertSearchResults(s, tr2, wantNew2)
}
testSearchOpWithLegacyIndexDBs(t, legacyData, newData, assertLegacyData, assertNewData)
}
func TestLegacyStorage_Search(t *testing.T) {
func TestLegacyStorage_SearchData(t *testing.T) {
genData := func(numMetrics int, prefix string, tr TimeRange) []MetricRow {
mrs := make([]MetricRow, numMetrics)
for i := range numMetrics {
@@ -295,14 +334,20 @@ func TestLegacyStorage_Search(t *testing.T) {
return mrs
}
const numMetrics = 1000
tr := TimeRange{
tr1 := TimeRange{
MinTimestamp: time.Date(2023, 6, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
MaxTimestamp: time.Date(2024, 5, 31, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
}
legacyData := genData(numMetrics, "legacy", tr)
newData := genData(numMetrics, "new", tr)
tr2 := TimeRange{
MinTimestamp: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
MaxTimestamp: time.Date(2024, 6, 30, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
}
legacyData := genData(numMetrics, "legacy", tr1)
new1Data := genData(numMetrics, "new1", tr1)
new2Data := genData(numMetrics, "new2", tr2)
newData := slices.Concat(new1Data, new2Data)
assertSearchResults := func(s *Storage, want []MetricRow) {
assertSearchResults := func(s *Storage, tr TimeRange, want []MetricRow) {
tfsAll := NewTagFilters()
if err := tfsAll.Add([]byte("__name__"), []byte(".*"), false, true); err != nil {
t.Fatalf("unexpected error in TagFilters.Add: %v", err)
@@ -314,13 +359,13 @@ func TestLegacyStorage_Search(t *testing.T) {
assertLegacyData := func(s *Storage) {
t.Helper()
want := legacyData
assertSearchResults(s, want)
assertSearchResults(s, tr1, legacyData)
}
assertNewData := func(s *Storage) {
t.Helper()
want := slices.Concat(legacyData, newData)
assertSearchResults(s, want)
want := slices.Concat(legacyData, new1Data)
assertSearchResults(s, tr1, want)
assertSearchResults(s, tr2, new2Data)
}
testSearchOpWithLegacyIndexDBs(t, legacyData, newData, assertLegacyData, assertNewData)
}