Compare commits

..

46 Commits

Author SHA1 Message Date
Vadim Alekseev
b118ffa5fe lib/httpserver: add support for listening on Unix domain socket
The syntax is `-httpListenAddr=unix:/path/to/file`.
This allows restricting access to HTTP API via filesystem permissions
and avoid TCP overhead.

`-tls` and `httpListenAddr.useProxyProtocol` flags cannot be used with
Unix domain sockets.

See [VictoriaLogs#1618](https://github.com/VictoriaMetrics/VictoriaLogs/issues/1618).
2026-08-20 00:22:54 +04:00
Evgeny
a441d7e94e app/vmalert-tool: reuse connections to -remoteWrite.url
DebugClient sends every series in a separate request, but it left
MaxIdleConnsPerHost at the two connections of http.DefaultTransport.
Under concurrent rule evaluation most requests could not find an idle
connection and had to dial a new one, leaving many sockets in TIME_WAIT
state.

Set MaxIdleConnsPerHost from the new -remoteWrite.maxIdleConnections
flag, mirroring -datasource.maxIdleConnections, and apply the already
existing -remoteWrite.idleConnTimeout to the transport.
In my tests 640 concurrent pushes now open 17 connections instead of
227.

Related PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11387/
2026-08-19 13:38:04 +02:00
Hui Wang
ac1d77e3de docs/changelog: add missing update note on v1.129.0 (#11430)
The breaking change was introduced with https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9779.
2026-08-19 12:05:23 +03:00
Max Kotliar
d149d4c91d app/vmselect: fix panic in sort_by_label_numeric() for label values with 309+ digit numbers (#11423)
`sort_by_label_numeric()` and `sort_by_label_numeric_desc()` call
`mustParseNum()`, which panics when `strconv.ParseFloat` returns
`ErrRange` for numbers with 309 or more digits.

Fix it by treating `ErrRange` as `Inf`, which is semantically correct
for sorting purposes.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/security/advisories/GHSA-9g98-8jgr-x2vv
PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11423
2026-08-18 18:55:21 +03:00
Max Kotliar
4946a403eb lib/protoparser: fix infinite loop on incomplete varint in Firehose ingestion endpoint (#11424)
When `binary.Uvarint` returns `(0, 0)` for an incomplete varint (e.g. a
single `0x80` byte), the parser loop made no progress and spun forever.

Fix it by treating `varIntLength <= 0` as an error.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/security/advisories/GHSA-89v2-864p-v3xc
PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11424

---------

Signed-off-by: Nikolay <nik@victoriametrics.com>
Co-authored-by: Nikolay <nik@victoriametrics.com>
2026-08-18 18:00:46 +03:00
Yury Moladau
a491b930e4 app/vmui: show selected time zone offset in header controls (#11332)
### Describe Your Changes

Make the selected timezone offset more visible and provide quicker access to timezone settings.

This makes the timezone immediately visible at the top when sharing metrics with others, which is especially useful during screen sharing. It also helps avoid ambiguity when sharing screenshots, where the timezone context may otherwise be unclear.

### Changes

- display the selected UTC offset in the header
- make the timezone shown in the date picker clickable
- update the mobile settings menu layout and styling

### Screenshots

<img width="623" height="54" alt="image"
src="https://github.com/user-attachments/assets/13500485-f038-4777-a99e-2ea8516fe024"
/>

<hr/>

| Before | After |
|---|---|
| <img width="404" height="430" alt="image"
src="https://github.com/user-attachments/assets/69bd9bbf-74f1-4cef-b423-094a2bbc77e8"
/> | <img width="404" height="430" alt="image"
src="https://github.com/user-attachments/assets/fde0e71f-4b7c-4a82-bca3-e43d4501daca"
/> |

---------

Signed-off-by: Yury Molodov <yurymolodov@gmail.com>
Signed-off-by: hagen1778 <roman@victoriametrics.com>
Co-authored-by: hagen1778 <roman@victoriametrics.com>
Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-08-18 17:48:52 +03:00
Pablo (Tomas) Fernandez
9caf74fbb2 docs: Update guide "VictoriaMetrics Multi-Regional Setup: Dedicated Monitoring" (#11334)
Updates the [VictoriaMetrics Multi-Regional Setup: Dedicated
Monitoring](https://docs.victoriametrics.com/guides/multi-regional-setup-dedicated-regions/)
guide.

* New diagrams using the official template
* Expanded sections with examples and config snippets

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
Co-authored-by: hagen1778 <roman@victoriametrics.com>
2026-08-18 14:51:50 +02:00
Roman Khavronenko
5e5ea9283e docs: explain new HA option with -vmselectAddr (#11410)
With single-node support of `-vmselectAddr` users can build an HA
topology using vmselect, that wasn't available before.
The new option is more preferable for data completeness but is more
resource-costly.

Adding docs how this can be achieved.

Related to https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11334

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
Signed-off-by: Roman Khavronenko <hagen1778@gmail.com>
Signed-off-by: Pablo (Tomas) Fernandez <46322567+TomFern@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: Pablo Fernandez <46322567+TomFern@users.noreply.github.com>
2026-08-18 14:51:20 +02:00
Cuong Le
7ba71f7f65 app/vmalert: fix a data race on the compress buffer after failed remote write requests
When the Go http client fails to send a request, the request body buffer
shouldn't be reused: the http transport may still be reading the body in
a separate goroutine even after the request returns (see
https://pkg.go.dev/net/http#RoundTripper). Reusing the buffer while that
goroutine is still running creates a data race.

This PR returns the compress buffer to the pool only when no send
attempt has failed. Otherwise the buffer is left for GC to collect.

A similar problem in VictoriaLogs was detected during the review of
https://github.com/VictoriaMetrics/VictoriaLogs/pull/1616.
2026-08-18 10:05:47 +02:00
Max Kotliar
f5b7c8795a dashboards: refine version annotation (#11397)
- Add the version annotation to the missing components (vmbackupmanager,
vmauth).
- Fix the incorrect placeholder: used `{{version}}`, while query
returned `{{short_version}}`.
- Update the query to include both `short_version` and `version`,
preferring short_version when available. Same as in
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11047.

Query used:
```
sum by(version) (
    label_replace(vm_app_version{job=~"$job", instance=~"$instance", short_version!=""}, "version", "$1", "short_version", "(.*)")
    OR
    vm_app_version{job=~"$job", instance=~"$instance", short_version=""}
) 
unless 
(
    sum by(version) (
        label_replace(vm_app_version{job=~"$job", instance=~"$instance", short_version!=""}, "version", "$1", "short_version", "(.*)")
        OR
        vm_app_version{job=~"$job", instance=~"$instance", short_version=""}
    ) offset $__interval
)
```

Previously, custom build versions were detected, but the version itself
wasn’t displayed:
<img width="799" height="389" alt="Screenshot 2026-08-12 at 21 21 12"
src="https://github.com/user-attachments/assets/bbfb0342-3f7a-4b2f-a8b8-4bbd04a9e1ab"
/>
2026-08-17 16:29:35 +03:00
f41gh7
28e138c0ae docs: update flags with actual v1.150.0 binaries
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2026-08-17 11:56:34 +02:00
f41gh7
c28aa8b7fc docs: bump version to v1.150.0
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2026-08-17 11:54:15 +02:00
f41gh7
c4566e7706 deplyoment/docker: bump version to v1.150.0
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2026-08-17 11:53:48 +02:00
f41gh7
16c8dd18ed docs: forward port LTS v1.136.16 changelog to upstream
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2026-08-17 11:52:24 +02:00
f41gh7
4ff25e5a28 docs: forward port LTS v1.148.2 changelog to upstream
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2026-08-17 11:51:48 +02:00
Artem Fetishev
14d6461aee lib/storage: fix search benchmarks (#11399)
Change benchmark time ranges:

- Several day timeranges: 1d, 2d, 4d, 8d, 16d, 32d, 64d to see how x2 time range increase affects the retrieval of a fixed number of metrics (100k).
- Several month time ranges: 1m, 2m, 4m. Same as above, but involves global index.

Related to #11388.

---------

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-08-17 07:50:48 +02:00
MarkHe1222
4edd8aa257 docs: fix grammar issues in README.md (#11409)
Signed-off-by: MarkHe1222 <20268851+MarkHe1222@users.noreply.github.com>
Co-authored-by: MarkHe1222 <20268851+MarkHe1222@users.noreply.github.com>
2026-08-15 22:40:22 +02:00
Pablo (Tomas) Fernandez
49b9c78460 docs: Add meta descriptions for SEO optimization (#11402)
This PR add meta descriptions to every page in the docs in this
repository. Right now, we have one general meta description for every
page. This PR adds page-specific descriptions.

Meta descriptions are not likely to improve rankings on search engines
but can improve CTRs. They can also be used to keep LLMs.txt updated as
new pages are added (by adding an automation later).

The descriptions were taken from LLMs.txt, so they were already reviewed
in https://github.com/VictoriaMetrics/vmdocs/pull/251

I only tweaked those that needed trimming to fit into the 160 SEO
character limit. We also revised them with our SEO specialist
(Jonathan). So I think it's a good staring point.

I'm going to open a PR for every repository that contributes to the docs
and add descriptions so eventually every page in the docs has a
dedicated meta descriptions.
2026-08-15 22:39:22 +02:00
Phuong Le
d712941d8c docs: adds VictoriaMetrics blog posts to documentation (#11249) 2026-08-15 22:38:48 +02:00
f41gh7
413f95d65f docs/changelog: cut release v1.150.0
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2026-08-14 14:26:29 +02:00
f41gh7
82a28a5fe3 docs: update version to v1.150.0
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2026-08-14 14:26:00 +02:00
f41gh7
7b8f01292d app/vmselect: run make vmui-update
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2026-08-14 14:24:06 +02:00
f41gh7
7c7dc7068d deployment/docker: update Go builder from Go1.26.5 to Go1.26.6
See https://github.com/golang/go/issues?q=milestone%3AGo1.26.6%20label%3ACherryPickApproved
2026-08-14 10:11:50 +02:00
Hui Wang
d0b202a50d app/vmalert: polish logs
1. Expose `series_fetched` in the rule debug log.
2. Add the group file path to the logs, so the group can be traced back
to a specific file when there are multiple groups with the same name.

Related PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11408
2026-08-14 09:39:13 +02:00
JAYICE
342bd964ce lib/workingsetcache: persist the prev cache on shutdown if prev cache is more valuable than curr cache
Previously workingsetcache will persist the curr cache on
shutdown regardless of cache miss rate, size or etc. It could cause slow
ingestion in vmstorage in slow query in vmselect if it was shutdown just
after the cache rotation.

 This commit saves cache in a split mode with lowest miss rate (prev or curr).

It prefers to presist prev cache when:
1. the cache miss ratio in curr cache is higher than 80%
2. the cache hit ratio in prec cache is higher than 20%.

fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11299
2026-08-14 09:35:38 +02:00
Evgenii M
690f02f19c lib/promrelabel: speed up matching multiple if expressions
When vmagent uses `-remoteWrite.urlRelabelConfig` to select metrics for a
particular `-remoteWrite.url`. Which is typical configuration for routing rules between multiple remote writes. And configs contain a `keep`
rule with a long list of `if` expressions:

```yaml
- action: keep
  if:
    - '{cluster="production",namespace=~"team-.+"}'
    - 'some_fat_metric{cluster="production"}'
```

At millions of samples per second, walking the full list for every
sample becomes expensive. This showed up as a noticeable part of
`vmagent` CPU usage in profiles.

This commit add fast path for selectors with an exact metric name at `IfExpression.Match`. Such as
`foo{bar="baz"}` or `{__name__="foo"}`. And it rejects the selector
before running the rest of its filters.


On production load this reduced `vmagent` CPU usage by roughly 15%.
The actual effect depends on the sample rate and on how many expressions
in the relabel config have distinct exact metric names.

 benchstat

```
goos: darwin
goarch: arm64
pkg: github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel
cpu: Apple M1 Pro
                                                             │    before    │                after                │
                                                             │    sec/op    │   sec/op     vs base                │
IfExpressionAlternatives/single_exact_match/labels_16-10        7.723n ± 0%   7.236n ± 3%   -6.32% (p=0.001 n=10)
IfExpressionAlternatives/single_exact_match/labels_48-10        7.697n ± 1%   7.286n ± 1%   -5.35% (p=0.000 n=10)
IfExpressionAlternatives/distinct_exact_6_miss/labels_16-10     30.25n ± 1%   12.14n ± 1%  -59.87% (p=0.000 n=10)
IfExpressionAlternatives/generic_6_miss/labels_16-10            75.70n ± 0%   78.09n ± 1%   +3.16% (p=0.000 n=10)
IfExpressionAlternatives/mixed_6_miss/labels_16-10              36.86n ± 0%   22.02n ± 1%  -40.27% (p=0.000 n=10)
IfExpressionAlternatives/distinct_exact_6_miss/labels_48-10     30.37n ± 0%   12.18n ± 0%  -59.88% (p=0.000 n=10)
IfExpressionAlternatives/generic_6_miss/labels_48-10            160.4n ± 1%   162.0n ± 0%   +1.03% (p=0.001 n=10)
IfExpressionAlternatives/mixed_6_miss/labels_48-10              50.76n ± 1%   35.13n ± 0%  -30.79% (p=0.000 n=10)
IfExpressionAlternatives/distinct_exact_32_miss/labels_16-10   158.20n ± 1%   53.80n ± 1%  -65.99% (p=0.000 n=10)
IfExpressionAlternatives/generic_32_miss/labels_16-10           365.4n ± 1%   383.6n ± 0%   +4.98% (p=0.000 n=10)
IfExpressionAlternatives/mixed_32_miss/labels_16-10            185.15n ± 1%   96.99n ± 1%  -47.61% (p=0.000 n=10)
IfExpressionAlternatives/distinct_exact_32_miss/labels_48-10   156.40n ± 1%   54.21n ± 1%  -65.34% (p=0.000 n=10)
IfExpressionAlternatives/generic_32_miss/labels_48-10           810.1n ± 2%   836.9n ± 0%   +3.30% (p=0.000 n=10)
IfExpressionAlternatives/mixed_32_miss/labels_48-10             247.5n ± 1%   158.9n ± 0%  -35.82% (p=0.000 n=10)
geomean                                                         79.69n        52.15n       -34.56%
```

Related PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11341/
2026-08-14 09:32:28 +02:00
Cody Kaczynski
f9df52255e lib/promscrape: add Linode service discovery
This commit implements Linode Service Discovery based on [Prometheus Linode discovery](https://github.com/prometheus/prometheus/tree/main/discovery/linode).

```
scrape_configs:
  - job_name: linode
    linode_sd_configs:
      - port: 9100
        authorization:
          credentials_file: /root/linode-sd-test/token
```

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9118
2026-08-14 09:26:16 +02:00
Fred Navruzov
c9e85290df docs: update vmanomaly for v1.30.2 (#11400)
## Summary

- update vmanomaly documentation and deployment examples for v1.30.2 and
VMUI v1.8.2
- move stable KPI policies to `reader.queries`, document model-level
compatibility fallbacks as deprecated, and update recommended examples
to online models with bootstrap-only fit schedules
- document bounded reader concurrency, native numerical-thread limits,
fit-data cleanup, grouped multivariate memory improvements, and
order-independent multivariate inference
- record v1.30.2 persisted-state compatibility with v1.30.1
- updated some diagrams for better UX
2026-08-13 22:41:23 +03: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
Nikolay
abdd6d853e lib/promscrape: properly detect IPv6 address for docker discovery
Previously service discovery was not able to discover IP address at IPv6
only networks.

 This commit adds fall-back for GlobalIPv6Address field at container
network.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10965
2026-08-12 18:24:44 +02:00
f41gh7
b4b14ede65 docs/changelog: re-order changelog entries 2026-08-12 18:20:20 +02:00
面壁
e31e58185c lib/netutil: avoid IPv6 hint for Unix socket errors
`NewStatDialFuncWithDial` currently appends an `-enableTCP6` suggestion
when the logical target address is not an IPv4 literal. For Unix domain
socket scrapes, that address is the HTTP target's `host:port`, while the
custom dialer connects to a captured socket path.

This produces misleading errors such as:

```text
dial unix /run/exporter.sock: connect: no such file or directory; try -enableTCP6 command-line flag for dialing ipv6 addresses
```

Remove the hint only when the actual dial error identifies a Unix
network address. The transport is obtained from the structured
`net.OpError` returned by the custom dialer, since it is not represented
by the logical address passed by `http.Transport`.

The existing TCP6 hint remains unchanged for standard TCP dialing and
other custom dialers, including proxy dialers. Regression tests cover
plain and wrapped Unix errors, TCP errors, and malformed `net.OpError`
values.

Related PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11320
2026-08-12 18:10:16 +02:00
Phuong Le
20ffe1f679 lib/logstorage: return discarded in-memory parts to pool
Returns interrupted or empty in-memory merge results to the pool

Related PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11340
2026-08-12 18:06:12 +02:00
Vandit Singh
7afd7c2a16 lib/protoparser/vmimport: skip labels with empty name
Sending a series with an empty label name to `/api/v1/import` stored it
under the wrong name. This request:

```
{"metric":{"__name__":"foo","":"bar","ok":"yes"},"values":[1],"timestamps":[456]}
```

was stored as `bar{ok="yes"}`. The name that was sent is gone and the
label value took its place. The response is 204 and nothing is logged.

This happens because a label with an empty name is equal to `__name__`,
as noted in `AddLabelBytes` in `app/vminsert/common/insert_ctx.go`. That
equivalence is intentional there, so the check belongs in the parser
instead.

`unmarshalTags` now skips tags with an empty name. Empty values are
still allowed, matching the Prometheus text parser, which allows them on
purpose per #453.

The other parsers already do this. The Prometheus text parser skips
empty names when it collects tags, and the graphite, influx and opentsdb
parsers skip empty tags too. This makes the JSON parser consistent with
them.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4962
2026-08-12 18:05:29 +02:00
Hui Wang
f8f87f316b vmselect: change HTTP response code from 422 to 400 when request parameters are missing or incorrect (#11348)
Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11330

Align with
https://prometheus.io/docs/prometheus/latest/querying/api/#format-overview,
return `400` instead of `422` when request parameters are missing or
incorrect and should be fixed on the caller side.
One exception is when the request hits a [resource
limit](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#resource-usage-limits),
such as `search.maxPointsPerTimeseries`; it could be fixed by the
caller(reducing the request range) or the admin(increasing the limit),
but it is not caused by incorrect parameters, so in this case, still
return 422.

---------

Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-08-12 14:29:20 +03:00
Max Kotliar
2777800bc2 app/vmagent: properly stop remotewrite in TestInsertHandler teardown (#11391)
The test was not calling `remotewrite.Stop()` in tearDown, which left
file descriptors for the persisted queue open. Running the test multiple
times would eventually exhaust fd limits and cause a panic.

Adding `remotewrite.Stop()` exposed a second issue:
`configReloaderStopCh` was initialized at package level, so it was
already closed after the first `Stop()` call. Re-initializing Init() (as
in `go test ./app/vmagent/prometheusimport/ -count=2`) would then panic
on a closed channel.

Fixed by moving `configReloaderStopCh` and `configReloaderWG`
initialization into Init(), so each Init/Stop cycle starts with a fresh
channel.

Decoupled from
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11354, where
@zasdaym originally identified the issue.
2026-08-12 12:24:00 +03:00
Victoria Nduka
398a3d74aa doc/victoriametrics: fix typos and grammar in keyConcepts.md (#11389)
This PR fixes a couple of typos and grammatical errors in the key
concepts doc for the VictoriaMetrics component.

---------

Signed-off-by: Victoria Nduka <122698422+nwanduka@users.noreply.github.com>
Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
Co-authored-by: Pablo Fernandez <46322567+TomFern@users.noreply.github.com>
2026-08-12 12:23:02 +03:00
Max Kotliar
aa32d59cc8 Revert "docs/changelog: add update note about vmalert logsqql breaking change"
This reverts commit 578754ef49.
2026-08-12 12:17:37 +03:00
Hui Wang
d4a40004ef app/vmalert: tolerate 400 evaluation error with -replay.continueWithExecutionErr in replay mode
Follow up https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11348

VM would then return `400` for missing or incorrect parameters, and
`422` for evaluation errors such as `duplicate time series on the left
side`, or when the request hits a [resource
limit](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#resource-usage-limits).
`-replay.continueWithExecutionErr` should work with both status codes.
2026-08-11 22:59:36 +02:00
Zhu Jiekun
8f4fdf0ae3 chore: unify the management of secret flags by app
fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11295
2026-08-11 22:58:16 +02:00
f41gh7
339a1b355c app/vminsert|vmselect|vmagent: enable --enableMultitenancyViaHeaders by default
This change aligns VictoriaMetrics multitenancy behavior with
VictoriaLogs and VictoriaTraces.

See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11365
2026-08-11 22:54:14 +02:00
245 changed files with 46767 additions and 995 deletions

View File

@@ -41,8 +41,8 @@ VictoriaMetrics is optimized for timeseries data, even when old time series are
* **Ideal for big data**: Works well with large amounts of time series data from APM, Kubernetes, IoT sensors, connected cars, industrial telemetry, financial data and various [Enterprise workloads](https://docs.victoriametrics.com/victoriametrics/enterprise/).
* **Query language**: Supports both PromQL and the more performant MetricsQL.
* **Easy to setup**: No dependencies, single [small binary](https://medium.com/@valyala/stripping-dependency-bloat-in-victoriametrics-docker-image-983fb5912b0d), configuration through command-line flags, but the default is also fine-tuned; backup and restore with [instant snapshots](https://medium.com/@valyala/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282).
* **Global query view**: Multiple Prometheus instances or any other data sources may ingest data into VictoriaMetrics and queried via a single query.
* **Various Protocols**: Support metric scraping, ingestion and backfilling in various protocol.
* **Global query view**: Multiple Prometheus instances or any other data sources may ingest data into VictoriaMetrics and be queried via a single query.
* **Various Protocols**: Support metric scraping, ingestion and backfilling in various protocols.
* [Prometheus exporters](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-scrape-prometheus-exporters-such-as-node-exporter), [Prometheus remote write API](https://docs.victoriametrics.com/victoriametrics/integrations/prometheus/), [Prometheus exposition format](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-import-data-in-prometheus-exposition-format).
* [InfluxDB line protocol](https://docs.victoriametrics.com/victoriametrics/integrations/influxdb/) over HTTP, TCP and UDP.
* [Graphite plaintext protocol](https://docs.victoriametrics.com/victoriametrics/integrations/graphite/#ingesting) with [tags](https://graphite.readthedocs.io/en/latest/tags.html#carbon).
@@ -78,7 +78,7 @@ We strictly apply security measures in everything we do. VictoriaMetrics has ach
Some good benchmarks VictoriaMetrics achieved:
* **Minimal memory footprint**: handling millions of unique timeseries with [10x less RAM](https://medium.com/@valyala/insert-benchmarks-with-inch-influxdb-vs-victoriametrics-e31a41ae2893) than InfluxDB, up to [7x less RAM](https://valyala.medium.com/prometheus-vs-victoriametrics-benchmark-on-node-exporter-metrics-4ca29c75590f) than Prometheus, Thanos or Cortex.
* **Highly scalable and performance** for [data ingestion](https://medium.com/@valyala/high-cardinality-tsdb-benchmarks-victoriametrics-vs-timescaledb-vs-influxdb-13e6ee64dd6b) and [querying](https://medium.com/@valyala/when-size-matters-benchmarking-victoriametrics-vs-timescale-and-influxdb-6035811952d4), [20x outperforms](https://medium.com/@valyala/insert-benchmarks-with-inch-influxdb-vs-victoriametrics-e31a41ae2893) InfluxDB and TimescaleDB.
* **Highly scalable and performant** for [data ingestion](https://medium.com/@valyala/high-cardinality-tsdb-benchmarks-victoriametrics-vs-timescaledb-vs-influxdb-13e6ee64dd6b) and [querying](https://medium.com/@valyala/when-size-matters-benchmarking-victoriametrics-vs-timescale-and-influxdb-6035811952d4), [20x outperforms](https://medium.com/@valyala/insert-benchmarks-with-inch-influxdb-vs-victoriametrics-e31a41ae2893) InfluxDB and TimescaleDB.
* **High data compression**: [70x more data points](https://medium.com/@valyala/when-size-matters-benchmarking-victoriametrics-vs-timescale-and-influxdb-6035811952d4) may be stored into limited storage than TimescaleDB, [7x less storage](https://valyala.medium.com/prometheus-vs-victoriametrics-benchmark-on-node-exporter-metrics-4ca29c75590f) space is required than Prometheus, Thanos or Cortex.
* **Reducing storage costs**: [10x more effective](https://docs.victoriametrics.com/victoriametrics/casestudies/#grammarly) than Graphite according to the Grammarly case study.
* **A single-node VictoriaMetrics** can replace medium-sized clusters built with competing solutions such as Thanos, M3DB, Cortex, InfluxDB or TimescaleDB. See [VictoriaMetrics vs Thanos](https://medium.com/@valyala/comparing-thanos-to-victoriametrics-cluster-b193bea1683), [Measuring vertical scalability](https://medium.com/@valyala/measuring-vertical-scalability-for-time-series-databases-in-google-cloud-92550d78d8ae), [Remote write storage wars - PromCon 2019](https://promcon.io/2019-munich/talks/remote-write-storage-wars/).
@@ -86,7 +86,7 @@ Some good benchmarks VictoriaMetrics achieved:
## Community and contributions
Feel free asking any questions regarding VictoriaMetrics:
Feel free to ask any questions regarding VictoriaMetrics:
* [Slack Inviter](https://slack.victoriametrics.com/) and [Slack channel](https://victoriametrics.slack.com/)
* [X (Twitter)](https://x.com/VictoriaMetrics/)

View File

@@ -25,7 +25,8 @@ import (
)
var (
httpListenAddrs = flagutil.NewArrayString("httpListenAddr", "TCP addresses to listen for incoming http requests. See also -tls and -httpListenAddr.useProxyProtocol")
httpListenAddrs = flagutil.NewArrayString("httpListenAddr", "Addresses to listen for incoming http requests. "+
"Use unix:/path/to/socket to listen on Unix domain socket. Note that -tls and -httpListenAddr.useProxyProtocol cannot be used with Unix sockets")
useProxyProtocol = flagutil.NewArrayBool("httpListenAddr.useProxyProtocol", "Whether to use proxy protocol for connections accepted at the corresponding -httpListenAddr . "+
"See https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt . "+
"With enabled proxy protocol http server cannot serve regular /metrics endpoint. Use -pushmetrics.url for metrics pushing")
@@ -63,6 +64,7 @@ func main() {
flag.CommandLine.SetOutput(os.Stdout)
flag.Usage = usage
envflag.Parse()
initSecretFlags()
buildinfo.Init()
logger.Init()
@@ -170,3 +172,9 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/
`
flagutil.Usage(s)
}
// initSecretFlags manages the secret flags for this app and must be called after flag parsing and before logger init.
func initSecretFlags() {
pushmetrics.InitSecretFlags()
vmselect.InitSecretFlags()
}

View File

@@ -51,9 +51,10 @@ import (
)
var (
httpListenAddrs = flagutil.NewArrayString("httpListenAddr", "TCP address to listen for incoming http requests. "+
httpListenAddrs = flagutil.NewArrayString("httpListenAddr", "Address to listen for incoming http requests. "+
"Set this flag to empty value in order to disable listening on any port. This mode may be useful for running multiple vmagent instances on the same server. "+
"Note that /targets and /metrics pages aren't available if -httpListenAddr=''. See also -tls and -httpListenAddr.useProxyProtocol")
"Note that /targets and /metrics pages aren't available if -httpListenAddr=''. "+
"Use unix:/path/to/socket to listen on Unix domain socket. Note that -tls and -httpListenAddr.useProxyProtocol cannot be used with Unix sockets")
useProxyProtocol = flagutil.NewArrayBool("httpListenAddr.useProxyProtocol", "Whether to use proxy protocol for connections accepted at the corresponding -httpListenAddr . "+
"See https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt . "+
"With enabled proxy protocol http server cannot serve regular /metrics endpoint. Use -pushmetrics.url for metrics pushing")
@@ -84,7 +85,7 @@ var (
maxLabelNameLen = flag.Int("maxLabelNameLen", 0, "The maximum length of label names in the accepted time series. Series with longer label name are ignored. In this case the vm_rows_ignored_total{reason=\"too_long_label_name\"} metric at /metrics page is incremented")
maxLabelValueLen = flag.Int("maxLabelValueLen", 0, "The maximum length of label values in the accepted time series. Series with longer label value are ignored. In this case the vm_rows_ignored_total{reason=\"too_long_label_value\"} metric at /metrics page is incremented")
enableMultitenancyViaHeaders = flag.Bool("enableMultitenancyViaHeaders", false, "Enables multitenancy via HTTP headers. "+
enableMultitenancyViaHeaders = flag.Bool("enableMultitenancyViaHeaders", true, "Enables multitenancy via HTTP headers. "+
"See https://docs.victoriametrics.com/victoriametrics/vmagent/#multitenancy")
)
@@ -115,7 +116,7 @@ func main() {
flag.CommandLine.SetOutput(os.Stdout)
flag.Usage = usage
envflag.Parse()
remotewrite.InitSecretFlags()
initSecretFlags()
buildinfo.Init()
logger.Init()
opentelemetry.Init()
@@ -843,3 +844,9 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/vmagent/ .
`
flagutil.Usage(s)
}
// initSecretFlags manages the secret flags for this app and must be called after flag parsing and before logger init.
func initSecretFlags() {
remotewrite.InitSecretFlags()
pushmetrics.InitSecretFlags()
}

View File

@@ -52,6 +52,7 @@ func setUp() {
func tearDown() {
protoparserutil.StopUnmarshalWorkers()
remotewrite.Stop()
srv.Close()
logger.ResetOutputForTest()
tmpDataDir := flag.Lookup("remoteWrite.tmpDataPath").Value.String()

View File

@@ -156,7 +156,8 @@ var maxQueues = cgroup.AvailableCPUs() * 16
const persistentQueueDirname = "persistent-queue"
// InitSecretFlags must be called after flag.Parse and before any logging.
// InitSecretFlags manages the secret flags for this pkg and must be called by app-level initSecretFlags.
// It should run before logger initialization and package Init() (if exists).
func InitSecretFlags() {
if !*showRemoteWriteURL {
// remoteWrite.url can contain authentication codes, so hide it at `/metrics` output.
@@ -245,6 +246,8 @@ func Init() {
dropDanglingQueues()
// Start config reloader.
configReloaderStopCh = make(chan struct{})
configReloaderWG = sync.WaitGroup{}
configReloaderWG.Go(func() {
for {
select {
@@ -331,7 +334,7 @@ func initRemoteWriteCtxs(urls []string) {
}
var (
configReloaderStopCh = make(chan struct{})
configReloaderStopCh chan struct{}
configReloaderWG sync.WaitGroup
)

View File

@@ -18,7 +18,7 @@ groups:
concurrency: 2
rules:
- alert: RequestErrorsToAPI
expr: increase(vm_http_request_errors_total{path=~".+"}[5m]) > 0
expr: increase(vm_http_request_errors_total[5m]) > 0
for: 15m
labels:
severity: warning

View File

@@ -60,7 +60,8 @@ var (
`Only valid for VictoriaMetrics as the datasource.`)
)
// InitSecretFlags must be called after flag.Parse and before any logging
// InitSecretFlags manages the secret flags for this pkg and must be called by app-level initSecretFlags.
// It should run before logger initialization and package Init() (if exists).
func InitSecretFlags() {
if !*showDatasourceURL {
flagutil.RegisterSecretFlag("datasource.url")

View File

@@ -59,7 +59,8 @@ absolute path to all .tpl files in root.
configCheckInterval = flag.Duration("configCheckInterval", 0, "Interval for checking for changes in '-rule', '-rule.templates' and '-notifier.config' files. "+
"By default, the checking is disabled. Send SIGHUP signal in order to force config check for changes.")
httpListenAddrs = flagutil.NewArrayString("httpListenAddr", "Address to listen for incoming http requests. See also -tls and -httpListenAddr.useProxyProtocol")
httpListenAddrs = flagutil.NewArrayString("httpListenAddr", "Address to listen for incoming http requests. "+
"Use unix:/path/to/socket to listen on Unix domain socket. Note that -tls and -httpListenAddr.useProxyProtocol cannot be used with Unix sockets")
useProxyProtocol = flagutil.NewArrayBool("httpListenAddr.useProxyProtocol", "Whether to use proxy protocol for connections accepted at the corresponding -httpListenAddr . "+
"See https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt . "+
"With enabled proxy protocol http server cannot serve regular /metrics endpoint. Use -pushmetrics.url for metrics pushing")
@@ -88,10 +89,7 @@ func main() {
flag.CommandLine.SetOutput(os.Stdout)
flag.Usage = usage
envflag.Parse()
remoteread.InitSecretFlags()
remotewrite.InitSecretFlags()
datasource.InitSecretFlags()
notifier.InitSecretFlags()
initSecretFlags()
buildinfo.Init()
logger.Init()
@@ -257,6 +255,9 @@ func getExternalURL(customURL string) (*url.URL, error) {
if len(*httpListenAddrs) > 0 {
listenAddr = (*httpListenAddrs)[0]
}
if strings.HasPrefix(listenAddr, "unix:") {
return nil, fmt.Errorf("-external.url must be set when -httpListenAddr is a unix socket")
}
isTLS := httpserver.IsTLS(0)
return getHostnameAsExternalURL(listenAddr, isTLS)
@@ -438,3 +439,12 @@ func getLastConfigError() error {
defer lastConfigErrMu.RUnlock()
return lastConfigErr
}
// initSecretFlags manages the secret flags for this app and must be called after flag parsing and before logger init.
func initSecretFlags() {
remoteread.InitSecretFlags()
remotewrite.InitSecretFlags()
datasource.InitSecretFlags()
notifier.InitSecretFlags()
pushmetrics.InitSecretFlags()
}

View File

@@ -189,7 +189,8 @@ func Init(extLabels map[string]string, extURL string) error {
return nil
}
// InitSecretFlags must be called after flag.Parse and before any logging
// InitSecretFlags manages the secret flags for this pkg and must be called by app-level initSecretFlags.
// It should run before logger initialization and package Init() (if exists).
func InitSecretFlags() {
if !*showNotifierURL {
flagutil.RegisterSecretFlag("notifier.url")

View File

@@ -56,7 +56,8 @@ var (
oauth2Scopes = flag.String("remoteRead.oauth2.scopes", "", "Optional OAuth2 scopes to use for -remoteRead.url. Scopes must be delimited by ';'.")
)
// InitSecretFlags must be called after flag.Parse and before any logging
// InitSecretFlags manages the secret flags for this pkg and must be called by app-level initSecretFlags.
// It should run before logger initialization and package Init() (if exists).
func InitSecretFlags() {
if !*showRemoteReadURL {
flagutil.RegisterSecretFlag("remoteRead.url")

View File

@@ -284,7 +284,15 @@ func (c *Client) flush(ctx context.Context, wr *prompb.WriteRequest) {
bb := writeRequestBufPool.Get()
bb.B = wr.MarshalProtobuf(bb.B[:0])
zb := compressBufPool.Get()
defer compressBufPool.Put(zb)
// A failed send may leave the http transport still reading zb.B in a separate goroutine
// even after send returns, so zb is returned to the pool only if no send attempt has failed.
// See https://pkg.go.dev/net/http#RoundTripper
sendFailed := false
defer func() {
if !sendFailed {
compressBufPool.Put(zb)
}
}()
if c.isVMRemoteWrite.Load() {
zb.B = zstd.CompressLevel(zb.B[:0], bb.B, 0)
} else {
@@ -303,10 +311,13 @@ func (c *Client) flush(ctx context.Context, wr *prompb.WriteRequest) {
L:
for {
err := c.send(ctx, zb.B)
if err != nil && (errors.Is(err, io.EOF) || netutil.IsTrivialNetworkError(err)) {
// Something in the middle between client and destination might be closing
// the connection. So we do a one more attempt in hope request will succeed.
err = c.send(ctx, zb.B)
if err != nil {
sendFailed = true
if errors.Is(err, io.EOF) || netutil.IsTrivialNetworkError(err) {
// Something in the middle between client and destination might be closing
// the connection. So we do a one more attempt in hope request will succeed.
err = c.send(ctx, zb.B)
}
}
if err == nil {
sentRows.Add(len(wr.Timeseries))

View File

@@ -36,6 +36,13 @@ func NewDebugClient() (*DebugClient, error) {
if err != nil {
return nil, fmt.Errorf("failed to create transport for -remoteWrite.url=%q: %w", *addr, err)
}
tr.IdleConnTimeout = *idleConnectionTimeout
// DebugClient sends every series in a separate request, so it needs more idle
// connections than the two http.DefaultTransport keeps per host.
tr.MaxIdleConnsPerHost = *maxIdleConnections
if tr.MaxIdleConns != 0 && tr.MaxIdleConns < tr.MaxIdleConnsPerHost {
tr.MaxIdleConns = tr.MaxIdleConnsPerHost
}
c := &DebugClient{
c: &http.Client{
Timeout: *sendTimeout,

View File

@@ -0,0 +1,45 @@
package remotewrite
import (
"net/http"
"testing"
)
// TestDebugClient_IdleConns makes sure DebugClient keeps enough idle connections
// to -remoteWrite.url. Every series is pushed in a separate request, so with the
// two idle connections per host of http.DefaultTransport most of the concurrent
// requests would open a new connection and leave a socket in TIME_WAIT state.
func TestDebugClient_IdleConns(t *testing.T) {
f := func(maxIdle int) {
t.Helper()
oldAddr, oldMaxIdle := *addr, *maxIdleConnections
*addr, *maxIdleConnections = "http://localhost:8428", maxIdle
defer func() {
*addr, *maxIdleConnections = oldAddr, oldMaxIdle
}()
client, err := NewDebugClient()
if err != nil {
t.Fatalf("failed to create debug client: %s", err)
}
tr, ok := client.c.Transport.(*http.Transport)
if !ok {
t.Fatalf("unexpected transport type %T", client.c.Transport)
}
if tr.MaxIdleConnsPerHost != maxIdle {
t.Fatalf("unexpected MaxIdleConnsPerHost; got %d; want %d", tr.MaxIdleConnsPerHost, maxIdle)
}
if tr.MaxIdleConns != 0 && tr.MaxIdleConns < maxIdle {
t.Fatalf("MaxIdleConns=%d is lower than MaxIdleConnsPerHost=%d", tr.MaxIdleConns, maxIdle)
}
if tr.IdleConnTimeout != *idleConnectionTimeout {
t.Fatalf("unexpected IdleConnTimeout; got %s; want %s", tr.IdleConnTimeout, *idleConnectionTimeout)
}
}
f(100)
// the number of idle connections must be raised together with the total limit
f(1000)
}

View File

@@ -34,6 +34,7 @@ var (
bearerTokenFile = flag.String("remoteWrite.bearerTokenFile", "", "Optional path to bearer token file to use for -remoteWrite.url.")
idleConnectionTimeout = flag.Duration("remoteWrite.idleConnTimeout", 50*time.Second, `Defines a duration for idle (keep-alive connections) to exist. Consider settings this value less to the value of "-http.idleConnTimeout". It must prevent possible "write: broken pipe" and "read: connection reset by peer" errors.`)
maxIdleConnections = flag.Int("remoteWrite.maxIdleConnections", 100, `Defines the number of idle (keep-alive connections) to -remoteWrite.url for the vmalert-tool debug writer, which sends every series in a separate request. Too low a value may result in a high number of sockets in TIME_WAIT state.`)
maxQueueSize = flag.Int("remoteWrite.maxQueueSize", defaultMaxQueueSize, "Defines the max number of pending datapoints to remote write endpoint")
maxBatchSize = flag.Int("remoteWrite.maxBatchSize", defaultMaxBatchSize, "Defines max number of timeseries to be flushed at once")
@@ -57,7 +58,8 @@ var (
oauth2Scopes = flag.String("remoteWrite.oauth2.scopes", "", "Optional OAuth2 scopes to use for -notifier.url. Scopes must be delimited by ';'.")
)
// InitSecretFlags must be called after flag.Parse and before any logging
// InitSecretFlags manages the secret flags for this pkg and must be called by app-level initSecretFlags.
// It should run before logger initialization and package Init() (if exists).
func InitSecretFlags() {
if !*showRemoteWriteURL {
flagutil.RegisterSecretFlag("remoteWrite.url")

View File

@@ -30,7 +30,8 @@ var (
"Progress bar rendering might be verbose or break the logs parsing, so it is recommended to be disabled when not used in interactive mode.")
ruleEvaluationConcurrency = flag.Int("replay.ruleEvaluationConcurrency", 1, "The maximum number of concurrent '/query_range' requests when replay recording rule or alerting rule with for=0. "+
"Increasing this value when replaying for a long time, since each request is limited by -replay.maxDatapointsPerQuery.")
continueWithExecutionErr = flag.Bool("replay.continueWithExecutionErr", false, "Whether to continue replaying other rules if a rule execution fails with a 422 response code, which can happen due to an expression syntax error or a resource limit being hit.")
continueWithExecutionErr = flag.Bool("replay.continueWithExecutionErr", false, "Whether to continue replaying other rules if a rule execution fails with a 400 or 422 response code, "+
"which can happen due to an expression syntax error or a resource limit being hit.")
)
func replay(groupsCfg []config.Group, qb datasource.QuerierBuilder, rw remotewrite.RWClient) (totalRows, droppedRows int, err error) {

View File

@@ -462,7 +462,11 @@ func (ar *AlertingRule) exec(ctx context.Context, ts time.Time, limit int) ([]pr
}
isPartial := isPartialResponse(res)
ar.logDebugf(ts, nil, "query returned %d series (elapsed: %s, isPartial: %t)", curState.Samples, curState.Duration, isPartial)
seriesFetched := 0
if res.SeriesFetched != nil {
seriesFetched = *res.SeriesFetched
}
ar.logDebugf(ts, nil, "query returned %d series (series_fetched: %d, elapsed: %s, isPartial: %t)", curState.Samples, seriesFetched, curState.Duration, isPartial)
qFn := func(query string) ([]datasource.Metric, error) {
res, _, err := ar.q.Query(ctx, query, ts)
return res.Data, err

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()
@@ -373,7 +375,7 @@ func (g *Group) Start(ctx context.Context, rw remotewrite.RWClient, rr datasourc
g.mu.Lock()
err := g.updateWith(ng)
if err != nil {
logger.Errorf("group %q: failed to update: %s", g.Name, err)
logger.Errorf("group %q (file=%q): failed to update: %s", g.Name, g.File, err)
g.mu.Unlock()
continue
}
@@ -412,7 +414,7 @@ func (g *Group) Start(ctx context.Context, rw remotewrite.RWClient, rr datasourc
errs := e.execConcurrently(ctx, g.Rules, ts, g.Concurrency, resolveDuration, g.Limit)
for err := range errs {
if err != nil {
logger.Errorf("group %q: %s", g.Name, err)
logger.Errorf("group %q (file=%q): %s", g.Name, g.File, err)
}
}
g.metrics.iterationDuration.UpdateDuration(start)
@@ -441,17 +443,17 @@ func (g *Group) Start(ctx context.Context, rw remotewrite.RWClient, rr datasourc
if rr != nil {
err := g.restore(ctx, rr, realEvalTS, *remoteReadLookBack)
if err != nil {
logger.Errorf("error while restoring ruleState for group %q: %s", g.Name, err)
logger.Errorf("error while restoring ruleState for group %q (file=%q): %s", g.Name, g.File, err)
}
}
for {
select {
case <-ctx.Done():
logger.Infof("group %q: context cancelled", g.Name)
logger.Infof("group %q (file=%q): context cancelled", g.Name, g.File)
return
case <-g.doneCh:
logger.Infof("group %q: received stop signal", g.Name)
logger.Infof("group %q (file=%q): received stop signal", g.Name, g.File)
return
case ng := <-g.updateCh:
g.mu.Lock()
@@ -465,7 +467,7 @@ func (g *Group) Start(ctx context.Context, rw remotewrite.RWClient, rr datasourc
err := g.updateWith(ng)
if err != nil {
logger.Errorf("group %q: failed to update: %s", g.Name, err)
logger.Errorf("group %q (file=%q): failed to update: %s", g.Name, g.File, err)
g.mu.Unlock()
continue
}
@@ -543,8 +545,8 @@ func (g *Group) delayBeforeStart(ts time.Time, maxDelay time.Duration) time.Dura
func (g *Group) infof(format string, args ...any) {
msg := fmt.Sprintf(format, args...)
logger.Infof("group %q %s; interval=%v; eval_offset=%v; concurrency=%d",
g.Name, msg, g.Interval, g.EvalOffset, g.Concurrency)
logger.Infof("group %q (file=%q; interval=%v; eval_offset=%v; concurrency=%d) %s",
g.Name, g.File, g.Interval, g.EvalOffset, g.Concurrency, msg)
}
// Replay performs group replay

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

@@ -208,7 +208,11 @@ func (rr *RecordingRule) exec(ctx context.Context, ts time.Time, limit int) ([]p
return nil, curState.Err
}
rr.logDebugf(ts, "query returned %d samples (elapsed: %s, isPartial: %t)", curState.Samples, curState.Duration, isPartialResponse(res))
seriesFetched := 0
if res.SeriesFetched != nil {
seriesFetched = *res.SeriesFetched
}
rr.logDebugf(ts, "query returned %d samples (series_fetched: %d, elapsed: %s, isPartial: %t)", curState.Samples, seriesFetched, curState.Duration, isPartialResponse(res))
qMetrics := res.Data
numSeries := len(qMetrics)

View File

@@ -132,9 +132,10 @@ func replayRule(r Rule, start, end time.Time, rw remotewrite.RWClient, replayRul
var esc *httpserver.ErrorWithStatusCode
if errors.As(err, &esc) {
statusCode := esc.StatusCode
// if the status code is 422, it means that the query was executed but failed due to an expression syntax error or a the resource limit being hit,
// continue replaying but skip the problematic execution if continueWithExecutionErr is true, otherwise, return the error without retry.
if statusCode == http.StatusUnprocessableEntity {
// if the status code is 400 or 422, the query failed due to reasons such as an expression syntax error or a resource limit being hit,
// rather than datasource unavailability.
// Continue replaying but skip the problematic execution if continueWithExecutionErr is true, otherwise, return the error without retry.
if statusCode == http.StatusUnprocessableEntity || statusCode == http.StatusBadRequest {
if continueWithExecutionErr {
logger.Errorf("rule %q: %s", r, err)
return 0, nil

View File

@@ -17,7 +17,6 @@ import (
"sync"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/jwt"
"github.com/VictoriaMetrics/metrics"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
@@ -27,6 +26,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/ioutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/jwt"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/procutil"
@@ -36,11 +36,13 @@ import (
)
var (
httpListenAddrs = flagutil.NewArrayString("httpListenAddr", "TCP address to listen for incoming http requests. "+
httpListenAddrs = flagutil.NewArrayString("httpListenAddr", "Address to listen for incoming http requests. "+
"By default, serves internal API and proxy requests. "+
" See also -tls, -httpListenAddr.useProxyProtocol and -httpInternalListenAddr.")
httpInternalListenAddr = flagutil.NewArrayString("httpInternalListenAddr", "TCP address to listen for incoming internal API http requests. Such as /health, /-/reload, /debug/pprof, etc. "+
"If flag is set, vmauth no longer serves internal API at -httpListenAddr.")
"Use unix:/path/to/socket to listen on Unix domain socket. Note that -tls and -httpListenAddr.useProxyProtocol cannot be used with Unix sockets. "+
"See also -httpInternalListenAddr")
httpInternalListenAddr = flagutil.NewArrayString("httpInternalListenAddr", "Address to listen for incoming internal API http requests. Such as /health, /-/reload, /debug/pprof, etc. "+
"If flag is set, vmauth no longer serves internal API at -httpListenAddr. "+
"Use unix:/path/to/socket to listen on Unix domain socket. Note that -tls and -httpListenAddr.useProxyProtocol cannot be used with Unix sockets")
useProxyProtocol = flagutil.NewArrayBool("httpListenAddr.useProxyProtocol", "Whether to use proxy protocol for connections accepted at the corresponding -httpListenAddr . "+
"See https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt . "+
"With enabled proxy protocol http server cannot serve regular /metrics endpoint. Use -pushmetrics.url for metrics pushing")
@@ -96,6 +98,7 @@ func main() {
flag.CommandLine.SetOutput(os.Stdout)
flag.Usage = usage
envflag.Parse()
initSecretFlags()
buildinfo.Init()
logger.Init()
@@ -911,3 +914,8 @@ func slowdownUnauthorizedResponse(r *http.Request) {
}
timerpool.Put(t)
}
// initSecretFlags manages the secret flags for this app and must be called after flag parsing and before logger init.
func initSecretFlags() {
pushmetrics.InitSecretFlags()
}

View File

@@ -27,7 +27,8 @@ import (
)
var (
httpListenAddr = flag.String("httpListenAddr", ":8420", "TCP address for exporting metrics at /metrics page")
httpListenAddr = flag.String("httpListenAddr", ":8420", "Address for exporting metrics at /metrics page. "+
"Use unix:/path/to/socket to listen on Unix domain socket")
storageDataPath = flag.String("storageDataPath", "victoria-metrics-data", "Path to VictoriaMetrics data. Must match -storageDataPath from VictoriaMetrics or vmstorage")
snapshotName = flag.String("snapshotName", "", "Name for the snapshot to backup. See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-work-with-snapshots. There is no need in setting -snapshotName if -snapshot.createURL is set")
snapshotCreateURL = flag.String("snapshot.createURL", "", "VictoriaMetrics create snapshot url. When this is given a snapshot will automatically be created during backup. "+
@@ -47,9 +48,8 @@ func main() {
// Write flags and help message to stdout, since it is easier to grep or pipe.
flag.CommandLine.SetOutput(os.Stdout)
flag.Usage = usage
flagutil.RegisterSecretFlag("snapshot.createURL")
flagutil.RegisterSecretFlag("snapshot.deleteURL")
envflag.Parse()
initSecretFlags()
buildinfo.Init()
logger.Init()
@@ -273,3 +273,10 @@ func newRemoteOriginFS(ctx context.Context) (common.RemoteFS, error) {
}
return fs, nil
}
// initSecretFlags manages the secret flags for this app and must be called after flag parsing and before logger init.
func initSecretFlags() {
flagutil.RegisterSecretFlag("snapshot.createURL")
flagutil.RegisterSecretFlag("snapshot.deleteURL")
pushmetrics.InitSecretFlags()
}

View File

@@ -47,6 +47,7 @@ func main() {
start := time.Now()
beforeFn := func(c *cli.Context) error {
flag.Parse()
initSecretFlags()
logger.Init()
isSilent = c.Bool(globalSilent)
if c.Bool(globalDisableProgressBar) {
@@ -619,3 +620,8 @@ func initConfigVM(c *cli.Context) (vm.Config, error) {
Backoff: bf,
}, nil
}
// initSecretFlags manages the secret flags for this app and must be called after flag parsing and before logger init.
func initSecretFlags() {
pushmetrics.InitSecretFlags()
}

View File

@@ -20,8 +20,9 @@ import (
)
var (
httpListenAddr = flag.String("httpListenAddr", ":8421", "TCP address for exporting metrics at /metrics page")
src = flag.String("src", "", "Source path with backup on the remote storage. "+
httpListenAddr = flag.String("httpListenAddr", ":8421", "Address for exporting metrics at /metrics page. "+
"Use unix:/path/to/socket to listen on Unix domain socket")
src = flag.String("src", "", "Source path with backup on the remote storage. "+
"Example: gs://bucket/path/to/backup, s3://bucket/path/to/backup, azblob://container/path/to/backup or fs:///path/to/local/backup\n"+
"Note: If custom S3 endpoint is used, URL should contain only name of the bucket, while hostname of S3 server must be specified via the -customS3Endpoint command-line flag.")
storageDataPath = flag.String("storageDataPath", "victoria-metrics-data", "Destination path where backup must be restored. "+
@@ -38,6 +39,7 @@ func main() {
flag.CommandLine.SetOutput(os.Stdout)
flag.Usage = usage
envflag.Parse()
initSecretFlags()
buildinfo.Init()
logger.Init()
@@ -112,3 +114,8 @@ func newSrcFS(ctx context.Context) (common.RemoteFS, error) {
}
return fs, nil
}
// initSecretFlags manages the secret flags for this app and must be called after flag parsing and before logger init.
func initSecretFlags() {
pushmetrics.InitSecretFlags()
}

View File

@@ -59,6 +59,12 @@ func Init(vmselectMaxConcurrentRequests int, vmselectMaxQueueDuration time.Durat
initVMUIConfig()
vmalertproxy.Init(*vmalertProxyURL)
}
// InitSecretFlags manages the secret flags for this pkg and must be called by app-level initSecretFlags.
// It should run before logger initialization and package Init() (if exists).
func InitSecretFlags() {
flagutil.RegisterSecretFlag("vmalert.proxyURL")
}

View File

@@ -516,7 +516,7 @@ func DeleteHandler(startTime time.Time, r *http.Request) error {
cp.deadline = searchutil.GetDeadlineForDelete(r, startTime)
if !cp.IsDefaultTimeRange() {
return fmt.Errorf("start=%d and end=%d args aren't supported. Remove these args from the query in order to delete all the matching metrics", cp.start, cp.end)
return fmt.Errorf("delete API does not support specific time ranges using start and end args, the series can only be deleted completely")
}
sq := storage.NewSearchQuery(cp.start, cp.end, cp.filterss, *maxDeleteSeries)
deletedCount, err := netstorage.DeleteSeries(nil, sq, cp.deadline)
@@ -540,11 +540,11 @@ func LabelValuesHandler(qt *querytracer.Tracer, startTime time.Time, labelName s
cp, err := getCommonParamsForLabelsAPI(r, startTime, false)
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
limit, err := httputil.GetInt(r, "limit")
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
sq := storage.NewSearchQuery(cp.start, cp.end, cp.filterss, *maxLabelsAPISeries)
@@ -584,7 +584,7 @@ func TSDBStatusHandler(qt *querytracer.Tracer, startTime time.Time, w http.Respo
cp, err := getCommonParams(r, startTime, false)
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
cp.deadline = searchutil.GetDeadlineForStatusRequest(r, startTime)
@@ -596,7 +596,7 @@ func TSDBStatusHandler(qt *querytracer.Tracer, startTime time.Time, w http.Respo
} else {
t, err := time.Parse("2006-01-02", dateStr)
if err != nil {
return fmt.Errorf("cannot parse `date` arg %q: %w", dateStr, err)
return httpserver.InvalidParamError(fmt.Errorf("cannot parse `date` arg %q: %w", dateStr, err))
}
date = uint64(t.Unix()) / secsPerDay
}
@@ -607,7 +607,7 @@ func TSDBStatusHandler(qt *querytracer.Tracer, startTime time.Time, w http.Respo
if len(topNStr) > 0 {
n, err := strconv.Atoi(topNStr)
if err != nil {
return fmt.Errorf("cannot parse `topN` arg %q: %w", topNStr, err)
return httpserver.InvalidParamError(fmt.Errorf("cannot parse `topN` arg %q: %w", topNStr, err))
}
if n <= 0 {
n = 1
@@ -645,11 +645,11 @@ func LabelsHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseW
cp, err := getCommonParamsForLabelsAPI(r, startTime, false)
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
limit, err := httputil.GetInt(r, "limit")
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
sq := storage.NewSearchQuery(cp.start, cp.end, cp.filterss, *maxLabelsAPISeries)
labels, err := netstorage.LabelNames(qt, sq, limit, cp.deadline)
@@ -671,10 +671,9 @@ func LabelsHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseW
//
// See https://prometheus.io/docs/prometheus/latest/querying/api/#querying-metric-metadata
func MetadataHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseWriter, r *http.Request) error {
limit, err := httputil.GetInt(r, "limit")
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
if limit < 0 {
limit = 0
@@ -734,11 +733,11 @@ func SeriesHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseW
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/91
cp, err := getCommonParamsForLabelsAPI(r, startTime, true)
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
limit, err := httputil.GetInt(r, "limit")
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
sq := storage.NewSearchQuery(cp.start, cp.end, cp.filterss, *maxSeriesLimit)
@@ -772,19 +771,19 @@ func QueryHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseWr
mayCache := !httputil.GetBool(r, "nocache")
query := r.FormValue("query")
if len(query) == 0 {
return fmt.Errorf("missing `query` arg")
return httpserver.InvalidParamError(fmt.Errorf("missing `query` arg"))
}
start, err := httputil.GetTime(r, "time", ct)
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
lookbackDelta, err := getMaxLookback(r)
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
step, err := httputil.GetDuration(r, "step", lookbackDelta)
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
if step <= 0 {
step = defaultStep
@@ -792,16 +791,16 @@ func QueryHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseWr
maxLen := searchutil.GetMaxQueryLen()
if len(query) > maxLen {
return fmt.Errorf("too long query; got %d bytes; mustn't exceed `-search.maxQueryLen=%d` bytes", len(query), maxLen)
return httpserver.InvalidParamError(fmt.Errorf("too long query; got %d bytes; mustn't exceed `-search.maxQueryLen=%d` bytes", len(query), maxLen))
}
etfs, err := searchutil.GetExtraTagFilters(r)
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
if childQuery, windowExpr, offsetExpr := promql.IsMetricSelectorWithRollup(query); childQuery != "" {
window, err := windowExpr.NonNegativeDuration(step)
if err != nil {
return fmt.Errorf("cannot parse lookbehind window in square brackets at %s: %w", query, err)
return httpserver.InvalidParamError(fmt.Errorf("cannot parse lookbehind window in square brackets at %s: %w", query, err))
}
offset := offsetExpr.Duration(step)
start -= offset
@@ -815,7 +814,7 @@ func QueryHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseWr
tagFilterss, err := getTagFilterssFromMatches([]string{childQuery})
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
filterss := searchutil.JoinTagFilterss(tagFilterss, etfs)
@@ -831,22 +830,25 @@ func QueryHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseWr
return nil
}
if childQuery, windowExpr, stepExpr, offsetExpr := promql.IsRollup(query); childQuery != "" {
if len(childQuery) > maxLen {
return httpserver.InvalidParamError(fmt.Errorf("too long query; got %d bytes; mustn't exceed `-search.maxQueryLen=%d` bytes", len(childQuery), maxLen))
}
newStep, err := stepExpr.NonNegativeDuration(step)
if err != nil {
return fmt.Errorf("cannot parse step in square brackets at %s: %w", query, err)
return httpserver.InvalidParamError(fmt.Errorf("cannot parse step in square brackets at %s: %w", query, err))
}
if newStep > 0 {
step = newStep
}
window, err := windowExpr.NonNegativeDuration(step)
if err != nil {
return fmt.Errorf("cannot parse lookbehind window in square brackets at %s: %w", query, err)
return httpserver.InvalidParamError(fmt.Errorf("cannot parse lookbehind window in square brackets at %s: %w", query, err))
}
offset := offsetExpr.Duration(step)
start -= offset
end := start
start = end - window
if err := queryRangeHandler(qt, startTime, w, childQuery, start, end, step, r, ct, etfs); err != nil {
if err := queryRangeHandler(qt, startTime, w, childQuery, start, end, step, lookbackDelta, r, ct, etfs); err != nil {
return fmt.Errorf("error when executing query=%q on the time range (start=%d, end=%d, step=%d): %w", childQuery, start, end, step, err)
}
return nil
@@ -854,7 +856,7 @@ func QueryHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseWr
queryOffset, err := getLatencyOffsetMilliseconds(r)
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
if !httputil.GetBool(r, "nocache") && ct-start < queryOffset && start-ct < queryOffset {
// Adjust start time only if `nocache` arg isn't set.
@@ -928,45 +930,43 @@ func QueryRangeHandler(qt *querytracer.Tracer, startTime time.Time, w http.Respo
ct := startTime.UnixNano() / 1e6
query := r.FormValue("query")
if len(query) == 0 {
return fmt.Errorf("missing `query` arg")
return httpserver.InvalidParamError(fmt.Errorf("missing `query` arg"))
}
maxLen := searchutil.GetMaxQueryLen()
if len(query) > maxLen {
return httpserver.InvalidParamError(fmt.Errorf("too long query; got %d bytes; mustn't exceed `-search.maxQueryLen=%d` bytes", len(query), maxLen))
}
start, err := httputil.GetTime(r, "start", ct-defaultStep)
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
end, err := httputil.GetTime(r, "end", ct)
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
step, err := httputil.GetDuration(r, "step", defaultStep)
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
etfs, err := searchutil.GetExtraTagFilters(r)
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
if err := queryRangeHandler(qt, startTime, w, query, start, end, step, r, ct, etfs); err != nil {
lookbackDelta, err := getMaxLookback(r)
if err != nil {
return httpserver.InvalidParamError(err)
}
if err := queryRangeHandler(qt, startTime, w, query, start, end, step, lookbackDelta, r, ct, etfs); err != nil {
return fmt.Errorf("error when executing query=%q on the time range (start=%d, end=%d, step=%d): %w", query, start, end, step, err)
}
return nil
}
func queryRangeHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseWriter, query string,
start, end, step int64, r *http.Request, ct int64, etfs [][]storage.TagFilter) error {
start, end, step, lookbackDelta int64, r *http.Request, ct int64, etfs [][]storage.TagFilter) error {
deadline := searchutil.GetDeadlineForQuery(r, startTime)
mayCache := !httputil.GetBool(r, "nocache")
optimizeRepeatedBinaryOpSubexprs := httputil.GetBool(r, "optimize_repeated_binary_op_subexprs")
lookbackDelta, err := getMaxLookback(r)
if err != nil {
return err
}
// Validate input args.
maxLen := searchutil.GetMaxQueryLen()
if len(query) > maxLen {
return fmt.Errorf("too long query; got %d bytes; mustn't exceed `-search.maxQueryLen=%d` bytes", len(query), maxLen)
}
if start > end {
end = start + defaultStep
}
@@ -1005,7 +1005,7 @@ func queryRangeHandler(qt *querytracer.Tracer, startTime time.Time, w http.Respo
if step < maxStepForPointsAdjustment.Milliseconds() {
queryOffset, err := getLatencyOffsetMilliseconds(r)
if err != nil {
return err
return httpserver.InvalidParamError(err)
}
if ct-queryOffset < end {
result = adjustLastPoints(result, ct-queryOffset, ct+step)
@@ -1156,13 +1156,13 @@ func QueryStatsHandler(w http.ResponseWriter, r *http.Request) error {
if len(topNStr) > 0 {
n, err := strconv.Atoi(topNStr)
if err != nil {
return fmt.Errorf("cannot parse `topN` arg %q: %w", topNStr, err)
return httpserver.InvalidParamError(fmt.Errorf("cannot parse `topN` arg %q: %w", topNStr, err))
}
topN = n
}
maxLifetimeMsecs, err := httputil.GetDuration(r, "maxLifetime", 10*60*1000)
if err != nil {
return fmt.Errorf("cannot parse `maxLifetime` arg: %w", err)
return httpserver.InvalidParamError(fmt.Errorf("cannot parse `maxLifetime` arg: %w", err))
}
maxLifetime := time.Duration(maxLifetimeMsecs) * time.Millisecond
w.Header().Set("Content-Type", "application/json")

View File

@@ -11,6 +11,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/querystats"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
@@ -46,15 +47,15 @@ func Exec(qt *querytracer.Tracer, ec *EvalConfig, q string, isFirstPointOnly boo
e, err := parsePromQLWithCache(q)
if err != nil {
return nil, err
return nil, httpserver.InvalidParamError(err)
}
if *disableImplicitConversion || *logImplicitConversion {
isInvalid := metricsql.IsLikelyInvalid(e)
if isInvalid && *disableImplicitConversion {
// we don't add query=%q to err message as it will be added by the caller
return nil, fmt.Errorf("query requires implicit conversion and is rejected according to -search.disableImplicitConversion command-line flag. " +
"See https://docs.victoriametrics.com/victoriametrics/metricsql/#implicit-query-conversions for details")
return nil, httpserver.InvalidParamError(fmt.Errorf("query requires implicit conversion and is rejected according to -search.disableImplicitConversion command-line flag. " +
"See https://docs.victoriametrics.com/victoriametrics/metricsql/#implicit-query-conversions for details"))
}
if isInvalid && *logImplicitConversion {
logger.Warnf("query=%q requires implicit conversion, see https://docs.victoriametrics.com/victoriametrics/metricsql/#implicit-query-conversions for details", e.AppendString(nil))

View File

@@ -2,6 +2,7 @@ package promql
import (
"bytes"
"errors"
"fmt"
"math"
"math/rand"
@@ -2566,6 +2567,11 @@ func isDecimalChar(ch byte) bool {
func mustParseNum(s string) float64 {
f, err := strconv.ParseFloat(s, 64)
if err != nil {
if errors.Is(err, strconv.ErrRange) {
// The number is too large to fit into float64; ParseFloat returns ±Inf in this case.
// Use ±Inf for sorting purposes — it is semantically correct.
return f
}
logger.Panicf("BUG: unexpected error when parsing the number %q: %s", s, err)
}
return f

View File

@@ -385,4 +385,12 @@ func TestNumericLess(t *testing.T) {
f("12.9", "12.56", false)
f("12.56", "12.9", true)
f("12.9", "12.9", false)
// 309-digit numbers - must not panic (regression test for GHSA-9g98-8jgr-x2vv)
big := strings.Repeat("9", 309)
f(big, "1", false)
f("1", big, true)
f(big, big, false)
f("-"+big, big, true)
f(big, "-"+big, false)
}

View File

@@ -0,0 +1,5 @@
<svg width="48" height="48" fill="#020202" xmlns="http://www.w3.org/2000/svg">
<path d="M24.5475 0C10.3246.0265251 1.11379 3.06365 4.40623 6.10077c0 0 12.32997 11.23333 16.58217 14.84083.8131.6896 2.1728 1.1936 3.5191 1.2201h.1199c1.3463-.0265 2.706-.5305 3.5191-1.2201 4.2522-3.5942 16.5422-14.84083 16.5422-14.84083C48.0478 3.06365 38.8636.0265251 24.6674 0"/>
<path d="M28.1579 27.0159c-.8131.6896-2.1728 1.1936-3.5191 1.2201h-.12c-1.3463-.0265-2.7059-.5305-3.519-1.2201-2.9725-2.5067-13.35639-11.87-17.26201-15.3979v5.4112c0 .5968.22661 1.3793.6265 1.7506C7.00358 21.1936 17.2675 30.5437 20.9731 33.6737c.8132.6896 2.1728 1.1936 3.5191 1.2201h.12c1.3463-.0265 2.7059-.5305 3.519-1.2201 3.679-3.13 13.9429-12.4536 16.6089-14.8939.4132-.3713.6265-1.1538.6265-1.7506V11.618c-3.9323 3.5411-14.3162 12.931-17.2354 15.3979h.0267Z"/>
<path d="M28.1579 39.748c-.8131.6897-2.1728 1.1937-3.5191 1.2202h-.12c-1.3463-.0265-2.7059-.5305-3.519-1.2202-2.9725-2.4933-13.35639-11.8567-17.26201-15.3978v5.4111c0 .5969.22661 1.3793.6265 1.7507C7.00358 33.9258 17.2675 43.2759 20.9731 46.4058c.8132.6897 2.1728 1.1937 3.5191 1.2202h.12c1.3463-.0265 2.7059-.5305 3.519-1.2202 3.679-3.1299 13.9429-12.4535 16.6089-14.8938.4132-.3714.6265-1.1538.6265-1.7507v-5.4111c-3.9323 3.5411-14.3162 12.931-17.2354 15.3978h.0267Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M24.5475 0C10.3246.0265251 1.11379 3.06365 4.40623 6.10077c0 0 12.32997 11.23333 16.58217 14.84083.8131.6896 2.1728 1.1936 3.5191 1.2201h.1199c1.3463-.0265 2.706-.5305 3.5191-1.2201 4.2522-3.5942 16.5422-14.84083 16.5422-14.84083C48.0478 3.06365 38.8636.0265251 24.6674 0" fill="#020202"/><path d="M28.1579 27.0159c-.8131.6896-2.1728 1.1936-3.5191 1.2201h-.12c-1.3463-.0265-2.7059-.5305-3.519-1.2201-2.9725-2.5067-13.35639-11.87-17.26201-15.3979v5.4112c0 .5968.22661 1.3793.6265 1.7506C7.00358 21.1936 17.2675 30.5437 20.9731 33.6737c.8132.6896 2.1728 1.1936 3.5191 1.2201h.12c1.3463-.0265 2.7059-.5305 3.519-1.2201 3.679-3.13 13.9429-12.4536 16.6089-14.8939.4132-.3713.6265-1.1538.6265-1.7506V11.618c-3.9323 3.5411-14.3162 12.931-17.2354 15.3979h.0267Z" fill="#020202"/><path d="M28.1579 39.748c-.8131.6897-2.1728 1.1937-3.5191 1.2202h-.12c-1.3463-.0265-2.7059-.5305-3.519-1.2202-2.9725-2.4933-13.35639-11.8567-17.26201-15.3978v5.4111c0 .5969.22661 1.3793.6265 1.7507C7.00358 33.9258 17.2675 43.2759 20.9731 46.4058c.8132.6897 2.1728 1.1937 3.5191 1.2202h.12c1.3463-.0265 2.7059-.5305 3.519-1.2202 3.679-3.1299 13.9429-12.4535 16.6089-14.8938.4132-.3714.6265-1.1538.6265-1.7507v-5.4111c-3.9323 3.5411-14.3162 12.931-17.2354 15.3978h.0267Z" fill="#020202"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="utf-8"/>
<link rel="icon" href="./favicon.svg"/>
<link rel="apple-touch-icon" href="./favicon.svg"/>
<link rel="mask-icon" href="./favicon.svg" color="#000000">
<link id="favicon" rel="icon" href="./assets/favicon.svg" />
<link rel="apple-touch-icon" href="./assets/favicon.svg" />
<link id="mask-icon" rel="mask-icon" href="./assets/favicon.svg" color="#000000">
<meta name="robots" content="noindex">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5"/>
@@ -37,11 +37,11 @@
<meta property="og:title" content="UI for VictoriaMetrics">
<meta property="og:url" content="https://victoriametrics.com/">
<meta property="og:description" content="Explore and troubleshoot your VictoriaMetrics data">
<script type="module" crossorigin src="./assets/index-B1dXK3k7.js"></script>
<script type="module" crossorigin src="./assets/index-BiDX4bB6.js"></script>
<link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-CNC7AqOf.js">
<link rel="modulepreload" crossorigin href="./assets/vendor-DwJYpOdw.js">
<link rel="stylesheet" crossorigin href="./assets/vendor-CnsZ1jie.css">
<link rel="stylesheet" crossorigin href="./assets/index-BJqoElx2.css">
<link rel="stylesheet" crossorigin href="./assets/index-CymA7XYg.css">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -3,7 +3,7 @@
"name": "vmui",
"icons": [
{
"src": "favicon.svg",
"src": "./assets/favicon.svg",
"sizes": "any",
"type": "image/svg+xml"
}

View File

@@ -1,4 +1,4 @@
FROM golang:1.26.5 AS build-web-stage
FROM golang:1.26.6 AS build-web-stage
COPY build /build
WORKDIR /build

View File

@@ -1,4 +1,4 @@
import { FC, useRef } from "preact/compat";
import { forwardRef, useImperativeHandle, useRef } from "preact/compat";
import ServerConfigurator from "./ServerConfigurator/ServerConfigurator";
import { ArrowDownIcon, SettingsIcon } from "../../Main/Icons";
import Button from "../../Main/Button/Button";
@@ -21,7 +21,11 @@ export interface ChildComponentHandle {
handleApply: () => void;
}
const GlobalSettings: FC = () => {
export interface GlobalSettingsHandle {
open: () => void;
}
const GlobalSettings = forwardRef<GlobalSettingsHandle>((_, ref) => {
const { isMobile } = useDeviceDetect();
const appModeEnable = getAppModeEnable();
@@ -74,6 +78,10 @@ const GlobalSettings: FC = () => {
},
].filter(control => control.show);
useImperativeHandle(ref, () => ({
open: handleOpen,
}));
return <>
{isMobile ? (
<div
@@ -139,6 +147,6 @@ const GlobalSettings: FC = () => {
</Modal>
)}
</>;
};
});
export default GlobalSettings;

View File

@@ -0,0 +1,52 @@
import { FC } from "preact/compat";
import Button from "../../../Main/Button/Button";
import { useTimeState } from "../../../../state/time/TimeStateContext";
import useDeviceDetect from "../../../../hooks/useDeviceDetect";
import { getUTCByTimezone } from "../../../../utils/time";
import { useMemo } from "react";
import { ArrowDownIcon, PlanetIcon } from "../../../Main/Icons";
type Props = {
onOpenSettings?: () => void;
}
const TimeZonePreview: FC<Props> = ({ onOpenSettings }) => {
const { isMobile } = useDeviceDetect();
const { timezone } = useTimeState();
const utcOffset = useMemo(() => getUTCByTimezone(timezone), [timezone]);
const handleOpenSettings = () => {
onOpenSettings && onOpenSettings();
};
if (isMobile) {
return (
<button
className="vm-mobile-option"
onClick={handleOpenSettings}
>
<span className="vm-mobile-option__icon"><PlanetIcon/></span>
<div className="vm-mobile-option-text">
<span className="vm-mobile-option-text__label">Time zone</span>
<span className="vm-mobile-option-text__value">{utcOffset}</span>
</div>
<span className="vm-mobile-option__arrow"><ArrowDownIcon/></span>
</button>
);
}
return (
<Button
className="vm-header-button"
onClick={handleOpenSettings}
startIcon={<PlanetIcon/>}
>
{utcOffset}
</Button>
);
};
export default TimeZonePreview;

View File

@@ -113,6 +113,8 @@ const StepConfigurator: FC = () => {
setError("");
}, [defaultStep, prevDefaultStep, value, graphDispatch]);
const textValue = isAutoStep ? `auto (${customStep})` : customStep;
return (
<div
className="vm-step-control"
@@ -126,7 +128,7 @@ const StepConfigurator: FC = () => {
<span className="vm-mobile-option__icon"><TimelineIcon/></span>
<div className="vm-mobile-option-text">
<span className="vm-mobile-option-text__label">Step</span>
<span className="vm-mobile-option-text__value">{customStep}</span>
<span className="vm-mobile-option-text__value">{textValue}</span>
</div>
<span className="vm-mobile-option__arrow"><ArrowDownIcon/></span>
</div>
@@ -138,7 +140,7 @@ const StepConfigurator: FC = () => {
startIcon={<TimelineIcon/>}
onClick={toggleOpenOptions}
>
Step: {isAutoStep ? `auto (${customStep})` : customStep}
Step: {textValue}
</Button>
)}
<Popper

View File

@@ -19,7 +19,11 @@ import useBoolean from "../../../../hooks/useBoolean";
import useWindowSize from "../../../../hooks/useWindowSize";
import usePrevious from "../../../../hooks/usePrevious";
export const TimeSelector: FC = () => {
type Props = {
onOpenSettings?: () => void;
}
export const TimeSelector: FC<Props> = ({ onOpenSettings }) => {
const { isMobile } = useDeviceDetect();
const { isDarkTheme } = useAppState();
const wrapperRef = useRef<HTMLDivElement>(null);
@@ -53,7 +57,7 @@ export const TimeSelector: FC = () => {
setFrom(formatDateForNativeInput(dateFromSeconds(start)));
}, [timezone, start]);
const setDuration = ({ duration, until, id }: {duration: string, until: Date, id: string}) => {
const setDuration = ({ duration, until, id }: { duration: string, until: Date, id: string }) => {
dispatch({ type: "SET_RELATIVE_TIME", payload: { duration, until, id } });
handleCloseOptions();
};
@@ -75,16 +79,23 @@ export const TimeSelector: FC = () => {
const setTimeAndClosePicker = () => {
if (from && until) {
dispatch({ type: "SET_PERIOD", payload: {
from: dayjs.tz(from).toDate(),
to: dayjs.tz(until).toDate()
} });
dispatch({
type: "SET_PERIOD", payload: {
from: dayjs.tz(from).toDate(),
to: dayjs.tz(until).toDate()
}
});
}
handleCloseOptions();
};
const onSwitchToNow = () => dispatch({ type: "RUN_QUERY_TO_NOW" });
const handleOpenSettings = () => {
onOpenSettings && onOpenSettings();
handleCloseOptions();
};
const onCancelClick = () => {
setUntil(formatDateForNativeInput(dateFromSeconds(end)));
setFrom(formatDateForNativeInput(dateFromSeconds(start)));
@@ -140,6 +151,7 @@ export const TimeSelector: FC = () => {
</Tooltip>
)}
</div>
<Popper
open={openOptions}
buttonRef={buttonRef}
@@ -179,13 +191,17 @@ export const TimeSelector: FC = () => {
onEnter={setTimeAndClosePicker}
/>
</div>
<div className="vm-time-selector-left-timezone">
<div className="vm-time-selector-left-timezone__title">{activeTimezone.region}</div>
<div className="vm-time-selector-left-timezone__utc">{activeTimezone.utc}</div>
</div>
<button
type="button"
className="vm-time-selector-left-timezone"
onClick={handleOpenSettings}
>
<span className="vm-time-selector-left-timezone__title">{activeTimezone.region}</span>
<span className="vm-time-selector-left-timezone__utc">{activeTimezone.utc}</span>
</button>
<Button
variant="text"
startIcon={<AlarmIcon />}
startIcon={<AlarmIcon/>}
onClick={onSwitchToNow}
>
switch to now

View File

@@ -40,8 +40,13 @@
gap: $padding-small;
font-size: $font-size-small;
margin-bottom: $padding-small;
color: $color-text;
cursor: pointer;
&__title {}
&:hover {
color: $color-primary;
text-decoration: underline;
}
&__utc {
display: inline-flex;

View File

@@ -634,6 +634,17 @@ export const DebugIcon = () => (
</svg>
);
export const PlanetIcon = () => (
<svg
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2M4 12c0-.61.08-1.21.21-1.78L8.99 15v1c0 1.1.9 2 2 2v1.93C7.06 19.43 4 16.07 4 12m13.89 5.4c-.26-.81-1-1.4-1.9-1.4h-1v-3c0-.55-.45-1-1-1h-6v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41C17.92 5.77 20 8.65 20 12c0 2.08-.81 3.98-2.11 5.4"
></path>
</svg>
);
export const SystemIcon = () => (
<svg
viewBox="0 0 24 24"

View File

@@ -11,6 +11,7 @@
&_mobile {
display: grid;
grid-template-columns: 1fr;
gap: 0;
padding: 0;
flex-grow: initial;

View File

@@ -6,9 +6,11 @@ import StepConfigurator from "../../components/Configurators/StepConfigurator/St
import { TimeSelector } from "../../components/Configurators/TimeRangeSettings/TimeSelector/TimeSelector";
import CardinalityDatePicker from "../../components/Configurators/CardinalityDatePicker/CardinalityDatePicker";
import { ExecutionControls } from "../../components/Configurators/TimeRangeSettings/ExecutionControls/ExecutionControls";
import GlobalSettings from "../../components/Configurators/GlobalSettings/GlobalSettings";
import GlobalSettings, { GlobalSettingsHandle } from "../../components/Configurators/GlobalSettings/GlobalSettings";
import ShortcutKeys from "../../components/Main/ShortcutKeys/ShortcutKeys";
import { ControlsProps } from "../Header/HeaderControls/HeaderControls";
import { useRef } from "react";
import TimeZonePreview from "../../components/Configurators/GlobalSettings/TimeZonePreview/TimeZonePreview";
const ControlsMainLayout: FC<ControlsProps> = ({
displaySidebar,
@@ -17,6 +19,7 @@ const ControlsMainLayout: FC<ControlsProps> = ({
accountIds,
closeModal,
}) => {
const settingsRef = useRef<GlobalSettingsHandle>(null);
return (
<div
@@ -27,14 +30,15 @@ const ControlsMainLayout: FC<ControlsProps> = ({
>
{headerSetup?.tenant && <TenantsConfiguration accountIds={accountIds || []}/>}
{headerSetup?.stepControl && <StepConfigurator/>}
{headerSetup?.timeSelector && <TimeSelector/>}
{headerSetup?.timeSelector && <TimeSelector onOpenSettings={() => settingsRef.current?.open()}/>}
{headerSetup?.cardinalityDatePicker && <CardinalityDatePicker/>}
<TimeZonePreview onOpenSettings={() => settingsRef.current?.open()}/>
{headerSetup?.executionControls && <ExecutionControls
tooltip={headerSetup?.executionControls?.tooltip}
useAutorefresh={headerSetup?.executionControls?.useAutorefresh}
closeModal={closeModal}
/>}
<GlobalSettings/>
<GlobalSettings ref={settingsRef}/>
{!displaySidebar && <ShortcutKeys/>}
</div>
);

View File

@@ -1,11 +1,12 @@
@use "src/styles/variables" as *;
.vm-mobile-option {
display: flex;
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
justify-content: flex-start;
gap: $padding-small;
padding: calc($padding-medium/2) 0;
gap: $padding-global;
padding: $padding-global $padding-small;
width: 100%;
user-select: none;
@@ -17,14 +18,33 @@
}
&__icon {
width: 22px;
height: 22px;
position: relative;
display: flex;
width: 40px;
height: 40px;
color: $color-primary;
&:after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.1;
background-color: currentColor;
border-radius: $border-radius-medium;
}
svg {
width: 21px;
height: auto;
}
}
&__arrow {
width: 14px;
height: 14px;
width: 20px;
height: 20px;
transform: rotate(-90deg);
color: $color-primary;
}
@@ -32,11 +52,13 @@
&-text {
display: grid;
align-items: center;
gap: 2px;
height: 100%;
gap: calc($padding-small / 2);
flex-grow: 1;
text-align: left;
&__label {
font-weight: bold;
font-weight: 600;
}
&__value {

View File

@@ -135,7 +135,7 @@ func tenantViaURL(addr, prefix, tenant, suffix string) string {
}
// tenantViaHeaders returns path in cluster's URL format where tenant is omitted in URL
// Only supported if -enableMultitenancyViaHeaders is specified
// Only supported if -enableMultitenancyViaHeaders is enabled
func tenantViaHeaders(addr, prefix, suffix string) string {
return fmt.Sprintf("http://%s/%s/%s", addr, prefix, suffix)
}

View File

@@ -25,12 +25,10 @@ func TestClusterMultiTenantSelectViaHeaders(t *testing.T) {
})
vminsert := tc.MustStartVminsert("vminsert", []string{
"-storageNode=" + vmstorage.VminsertAddr(),
"-enableMultitenancyViaHeaders",
})
vmselect := tc.MustStartVmselect("vmselect", []string{
"-storageNode=" + vmstorage.VmselectAddr(),
"-search.tenantCacheExpireDuration=0",
"-enableMultitenancyViaHeaders",
})
multitenant := make(http.Header)

View File

@@ -594,7 +594,6 @@ func TestSingleVMAgentMultitenancy(t *testing.T) {
fmt.Sprintf(`-remoteWrite.url=%s/api/v1/write`, remoteWriteSrv.URL),
"-remoteWrite.tmpDataPath=" + tc.Dir() + "/vmagent-multitenancy",
"-enableMultitenantHandlers",
"-enableMultitenancyViaHeaders",
})
vmagent.APIV1ImportPrometheus(t, []string{

View File

@@ -59,6 +59,19 @@
},
"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

@@ -37,7 +37,7 @@
"uid": "$ds"
},
"enable": true,
"expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset $__interval) by(version))",
"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 change",

View File

@@ -37,7 +37,7 @@
"uid": "$ds"
},
"enable": true,
"expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset $__interval) by(version))",
"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",

View File

@@ -60,6 +60,19 @@
},
"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(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset $__interval) by(version))",
"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 change",

View File

@@ -38,7 +38,7 @@
"uid": "$ds"
},
"enable": true,
"expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset $__interval) by(version))",
"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",

View File

@@ -26,11 +26,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 $__interval) by(short_version))",
"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": "{{short_version}}",
"textFormat": "{{version}}",
"titleFormat": "Version change"
},
{

View File

@@ -26,11 +26,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 $__interval) by(short_version))",
"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": "{{short_version}}",
"textFormat": "{{version}}",
"titleFormat": "Version change"
},
{

View File

@@ -54,6 +54,19 @@
},
"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(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(short_version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset $__interval) by(short_version))",
"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": "{{short_version}}",
"textFormat": "{{version}}",
"titleFormat": "Version change"
},
{

View File

@@ -25,11 +25,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 $__interval) by(short_version))",
"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": "{{short_version}}",
"textFormat": "{{version}}",
"titleFormat": "Version change"
},
{

View File

@@ -53,6 +53,19 @@
},
"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

@@ -7,7 +7,7 @@ ROOT_IMAGE ?= alpine:3.24.1
ROOT_IMAGE_SCRATCH ?= scratch
CERTS_IMAGE := alpine:3.24.1
GO_BUILDER_IMAGE := golang:1.26.5
GO_BUILDER_IMAGE := golang:1.26.6
BUILDER_IMAGE := local/builder:2.0.0-$(shell echo $(GO_BUILDER_IMAGE) | tr :/ __)-1
BASE_IMAGE := local/base:1.1.4-$(shell echo $(ROOT_IMAGE) | tr :/ __)-$(shell echo $(CERTS_IMAGE) | tr :/ __)

View File

@@ -3,7 +3,7 @@ services:
# It scrapes targets defined in --promscrape.config
# And forward them to --remoteWrite.url
vmagent:
image: victoriametrics/vmagent:v1.149.0
image: victoriametrics/vmagent:v1.150.0
depends_on:
- "vmauth"
ports:
@@ -42,14 +42,14 @@ services:
# vmstorage shards. Each shard receives 1/N of all metrics sent to vminserts,
# where N is number of vmstorages (2 in this case).
vmstorage-1:
image: victoriametrics/vmstorage:v1.149.0-cluster
image: victoriametrics/vmstorage:v1.150.0-cluster
volumes:
- strgdata-1:/storage
command:
- "--storageDataPath=/storage"
restart: always
vmstorage-2:
image: victoriametrics/vmstorage:v1.149.0-cluster
image: victoriametrics/vmstorage:v1.150.0-cluster
volumes:
- strgdata-2:/storage
command:
@@ -59,7 +59,7 @@ services:
# vminsert is ingestion frontend. It receives metrics pushed by vmagent,
# pre-process them and distributes across configured vmstorage shards.
vminsert-1:
image: victoriametrics/vminsert:v1.149.0-cluster
image: victoriametrics/vminsert:v1.150.0-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -68,7 +68,7 @@ services:
- "--storageNode=vmstorage-2:8400"
restart: always
vminsert-2:
image: victoriametrics/vminsert:v1.149.0-cluster
image: victoriametrics/vminsert:v1.150.0-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -80,7 +80,7 @@ services:
# vmselect is a query fronted. It serves read queries in MetricsQL or PromQL.
# vmselect collects results from configured `--storageNode` shards.
vmselect-1:
image: victoriametrics/vmselect:v1.149.0-cluster
image: victoriametrics/vmselect:v1.150.0-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -90,7 +90,7 @@ services:
- "--vmalert.proxyURL=http://vmalert:8880"
restart: always
vmselect-2:
image: victoriametrics/vmselect:v1.149.0-cluster
image: victoriametrics/vmselect:v1.150.0-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -105,7 +105,7 @@ services:
# read requests from Grafana, vmui, vmalert among vmselects.
# It can be used as an authentication proxy.
vmauth:
image: victoriametrics/vmauth:v1.149.0
image: victoriametrics/vmauth:v1.150.0
depends_on:
- "vmselect-1"
- "vmselect-2"
@@ -119,7 +119,7 @@ services:
# vmalert executes alerting and recording rules
vmalert:
image: victoriametrics/vmalert:v1.149.0
image: victoriametrics/vmalert:v1.150.0
depends_on:
- "vmauth"
ports:

View File

@@ -3,7 +3,7 @@ services:
# It scrapes targets defined in --promscrape.config
# And forward them to --remoteWrite.url
vmagent:
image: victoriametrics/vmagent:v1.149.0
image: victoriametrics/vmagent:v1.150.0
depends_on:
- "victoriametrics"
ports:
@@ -18,7 +18,7 @@ services:
# VictoriaMetrics instance, a single process responsible for
# storing metrics and serve read requests.
victoriametrics:
image: victoriametrics/victoria-metrics:v1.149.0
image: victoriametrics/victoria-metrics:v1.150.0
ports:
- 8428:8428
- 8089:8089
@@ -59,7 +59,7 @@ services:
# vmalert executes alerting and recording rules
vmalert:
image: victoriametrics/vmalert:v1.149.0
image: victoriametrics/vmalert:v1.150.0
depends_on:
- "victoriametrics"
- "alertmanager"

View File

@@ -75,7 +75,7 @@ groups:
Consider to limit the ingestion rate, decrease retention or scale the disk space if possible."
- alert: RequestErrorsToAPI
expr: increase(vm_http_request_errors_total{path=~".+", path!="*"}[5m]) > 0
expr: increase(vm_http_request_errors_total[5m]) > 0
for: 15m
labels:
severity: warning
@@ -83,24 +83,8 @@ groups:
annotations:
dashboard: "{{ $externalURL }}/d/oS7Bi_0Wz?viewPanel=52&var-instance={{ $labels.instance }}"
summary: "Too many errors served for {{ $labels.job }} path {{ $labels.path }} (instance {{ $labels.instance }})"
description: |
Requests to path {{ $labels.path }} are receiving errors.
Please verify if clients are sending correct requests.
# Auth errors and unknown paths should be handled by a different alert
# See https://github.com/VictoriaMetrics/VictoriaMetrics/blob/fdd9a221df835daa378ae2e6c9f12e4e3be79c76/lib/httpserver/httpserver.go#L589-L591
- alert: RequestErrorsToUnknownPaths
expr: sum(increase(vm_http_request_errors_total{path=~"^(\*|)$"}[5m])) by(job, instance, reason) > 0
for: 15m
labels:
severity: warning
show_at: dashboard
annotations:
dashboard: "{{ $externalURL }}/d/oS7Bi_0Wz?viewPanel=52&var-instance={{ $labels.instance }}"
summary: "Too many errors served for {{ $labels.job }} with reason {{ $labels.reason }} (instance {{ $labels.instance }})"
description: |
Requests are failing with reason {{ $labels.reason }}.
Please verify if clients are sending correct requests.
description: "Requests to path {{ $labels.path }} are receiving errors.
Please verify if clients are sending correct requests."
- alert: RPCErrors
expr: |

View File

@@ -75,7 +75,7 @@ groups:
Consider to limit the ingestion rate, decrease retention or scale the disk space if possible."
- alert: RequestErrorsToAPI
expr: increase(vm_http_request_errors_total{path=~".+"}[5m]) > 0
expr: increase(vm_http_request_errors_total[5m]) > 0
for: 15m
labels:
severity: warning
@@ -85,21 +85,6 @@ groups:
description: "Requests to path {{ $labels.path }} are receiving errors.
Please verify if clients are sending correct requests."
# Auth errors and unknown paths should be handled by a different alert
# See https://github.com/VictoriaMetrics/VictoriaMetrics/blob/fdd9a221df835daa378ae2e6c9f12e4e3be79c76/lib/httpserver/httpserver.go#L589-L591
- alert: RequestErrorsToUnknownPaths
expr: sum(increase(vm_http_request_errors_total{path=~"^(\*|)$"}[5m])) by(job, instance, reason) > 0
for: 15m
labels:
severity: warning
show_at: dashboard
annotations:
dashboard: "{{ $externalURL }}/d/oS7Bi_0Wz?viewPanel=52&var-instance={{ $labels.instance }}"
summary: "Too many errors served for {{ $labels.job }} with reason {{ $labels.reason }} (instance {{ $labels.instance }})"
description: |
Requests are failing with reason {{ $labels.reason }}.
Please verify if clients are sending correct requests.
- alert: TooHighChurnRate
expr: |
(

View File

@@ -1,6 +1,6 @@
services:
vmagent:
image: victoriametrics/vmagent:v1.149.0
image: victoriametrics/vmagent:v1.150.0
depends_on:
- "victoriametrics"
ports:
@@ -14,7 +14,7 @@ services:
restart: always
victoriametrics:
image: victoriametrics/victoria-metrics:v1.149.0
image: victoriametrics/victoria-metrics:v1.150.0
ports:
- 8428:8428
volumes:
@@ -40,7 +40,7 @@ services:
restart: always
vmalert:
image: victoriametrics/vmalert:v1.149.0
image: victoriametrics/vmalert:v1.150.0
depends_on:
- "victoriametrics"
ports:
@@ -59,7 +59,7 @@ services:
- '--external.alert.source=explore?orgId=1&left=["now-1h","now","VictoriaMetrics",{"expr": },{"mode":"Metrics"},{"ui":[true,true,true,"none"]}]'
restart: always
vmanomaly:
image: victoriametrics/vmanomaly:v1.30.1
image: victoriametrics/vmanomaly:v1.30.2
depends_on:
- "victoriametrics"
ports:

View File

@@ -1,7 +1,7 @@
schedulers:
periodic:
infer_every: "1m"
fit_every: "100w" # the online model keeps learning during inference
fit_every: "1000d" # bootstrap-only schedule; use a finite cadence if accumulated state must be reset
fit_window: "2w"
models:

View File

@@ -1,5 +1,6 @@
---
title: AI tools
description: "MCP servers, skills, and AI assistant integrations for querying metrics, logs, and traces with natural language."
weight: 61
menu:
docs:

View File

@@ -1,6 +1,7 @@
---
weight: 7
title: CHANGELOG
description: "Release history for vmanomaly."
menu:
docs:
identifier: "vmanomaly-changelog"
@@ -16,6 +17,23 @@ Please find the changelog for VictoriaMetrics Anomaly Detection below.
{{% collapse name="2026" open=true %}}
## v1.30.2
Released: 2026-08-13
- UI: Updated [vmanomaly UI](https://docs.victoriametrics.com/anomaly-detection/ui/) from [v1.8.1](https://docs.victoriametrics.com/anomaly-detection/ui/#v181) to [v1.8.2](https://docs.victoriametrics.com/anomaly-detection/ui/#v182), fixing tenant discovery and switching for multitenant VictoriaMetrics datasources.
- FEATURE: Added **query**-level [`data_range`, `detection_direction`, `min_dev_from_expected`, and `min_rel_dev_from_expected`](https://docs.victoriametrics.com/anomaly-detection/components/reader/#per-query-parameters). Model-level placement is deprecated but remains a compatible fallback.
- IMPROVEMENT: Added [`reader.workers`](https://docs.victoriametrics.com/anomaly-detection/components/reader/#config-parameters) to cap concurrent datasource requests and disk-streamed query chunks; `0` selects an automatic bound.
- IMPROVEMENT: Added [`settings.native_threads_per_worker`](https://docs.victoriametrics.com/anomaly-detection/components/settings/#parallelization) to reduce [native-thread oversubscription](https://scikit-learn.org/stable/computing/parallelism.html#oversubscription-spawning-too-many-threads), throttling risk, fit latency, and memory. For example, with 16 CPUs/workers, [Temporal Envelope](https://docs.victoriametrics.com/anomaly-detection/components/models/#temporal-envelope) fit time fell 70.6% for 1,000 univariate models and 11.5% for 100 x 10-channel grouped models; inference was unchanged.
- IMPROVEMENT: Removed temporary fit-data generations after all dependent models finish and commit, while safely retaining failed or overlapping generations.
- IMPROVEMENT: Reduced disk-backed grouped multivariate memory and fit latency without model or state migration. For example, 100 x 100-channel four-week fits cut peak PSS/fit time by 63%/56% for [Temporal Envelope](https://docs.victoriametrics.com/anomaly-detection/components/models/#temporal-envelope).
- BUGFIX: Made [multivariate models](https://docs.victoriametrics.com/anomaly-detection/components/models/#multivariate-models) independent of input channel order when the fitted channel set matches; missing, extra, or duplicate channels remain rejected.
## v1.30.1
Released: 2026-08-06

View File

@@ -1,6 +1,7 @@
---
weight: 6
title: FAQ
description: "Frequently asked questions about vmanomaly."
menu:
docs:
identifier: "vmanomaly-faq"
@@ -33,7 +34,7 @@ Please see example graph illustrating this logic below:
![anomaly-score-calculation-example](vmanomaly-prophet-example.webp)
> p.s. please note that additional post-processing logic might be applied to produced anomaly scores, if common arguments like [`min_dev_from_expected`](https://docs.victoriametrics.com/anomaly-detection/components/models/#minimal-deviation-from-expected) or [`detection_direction`](https://docs.victoriametrics.com/anomaly-detection/components/models/#detection-direction) are enabled for a particular model. Follow the links above for the explanations.
> Additional post-processing logic may be applied to produced anomaly scores when query policies such as [`min_dev_from_expected`](https://docs.victoriametrics.com/anomaly-detection/components/models/#minimal-deviation-from-expected) or [`detection_direction`](https://docs.victoriametrics.com/anomaly-detection/components/models/#detection-direction) are configured. Follow the links for details.
## How does vmanomaly work?
@@ -135,7 +136,7 @@ Still not 100% sure what to use? We are [here to help](https://docs.victoriametr
## Incorporating domain knowledge
Anomaly detection models can significantly improve when incorporating business-specific assumptions about the data and what constitutes an anomaly. `vmanomaly` supports various [business-side configuration parameters](https://docs.victoriametrics.com/anomaly-detection/components/models/#common-args) across all built-in models to **reduce [false positives](https://victoriametrics.com/blog/victoriametrics-anomaly-detection-handbook-chapter-1/#false-positive)** and **align model behavior with business needs**, for example:
Anomaly detection models can significantly improve when incorporating business-specific assumptions about the data and what constitutes an anomaly. `vmanomaly` supports [business policies](https://docs.victoriametrics.com/anomaly-detection/components/models/#common-args) across built-in models to **reduce [false positives](https://victoriametrics.com/blog/victoriametrics-anomaly-detection-handbook-chapter-1/#false-positive)** and **align model behavior with business needs**, for example:
- **Setting `detection_direction`** - use [`detection_direction`](https://docs.victoriametrics.com/anomaly-detection/components/models/#detection-direction) to specify whether anomalies occur **above or below expectations**:
- Set to `above_expected` for metrics like error rates, where spikes indicate anomalies.
@@ -163,7 +164,7 @@ Then, the following config may be used to benefit from incorporating domain know
schedulers:
periodic_http:
class: periodic
fit_every: 12w
fit_every: 1000d
fit_window: 1w
infer_every: 1m
# other schedulers ...
@@ -172,18 +173,19 @@ reader:
queries:
percentage_4xx:
expr: respective_metricsQL_expr
data_range: [0, 0.05] # to automatically trigger anomaly score > 1 for error rates > 5%
data_range: [0, 0.05] # query-level business policy from v1.30.2; error rates >5% trigger anomaly score >1
detection_direction: 'above_expected' # query-level from v1.30.2; only spikes are anomalous
min_dev_from_expected: [0, 0.005] # query-level from v1.30.2; ignore upward deviations below 0.5%
min_rel_dev_from_expected: [0, 10] # query-level from v1.30.2; ignore upward deviations below 10%
step: 1m
models:
# other models ...
zscore: # let it be online Z-score, for simplicity
class: zscore_online # online model update itself each infer call, resulting in resource-efficient setups
z_threshold: 3.0
decay: 0.99 # give more weight to recent data while using the bootstrap-only fit schedule
schedulers: ['periodic_http']
queries: ['percentage_4xx']
detection_direction: 'above_expected' # as interested only in spikes, drops are OK
min_dev_from_expected: [0, 0.005] # <0.5% deviations vs expected values should be neglected, generating anomaly score == 0
min_rel_dev_from_expected: [0, 0.1] # <10% relative deviations vs expected values should be neglected, generating anomaly score == 0
# to align predictions to be within [0, 5%] interval, defined in reader.queries.percentage_4xx.data_range
clip_predictions: True
# specify output series produced by vmanomaly to be written to VictoriaMetrics in `writer`
@@ -229,7 +231,7 @@ models:
schedulers: ['scheduler_alias'] # if omitted, all the defined schedulers will be attached
queries: ['query_alias1'] # if omitted, all the defined queries will be attached
# https://docs.victoriametrics.com/anomaly-detection/components/models/#provide-series
provide_series: ['anomaly_score']
provide_series: ['anomaly_score']
# ... other models
reader:
@@ -255,6 +257,7 @@ Configuration above will produce N intervals of full length (`fit_window`=14d +
`vmanomaly` can generate future forecasts with [Temporal Envelope](https://docs.victoriametrics.com/anomaly-detection/components/models/#temporal-envelope) {{% available_from "v1.30.0" anomaly %}}, the preferred online forecasting model. [ProphetModel](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet) {{% available_from "v1.25.3" anomaly %}} also supports forecasting for existing offline configurations. Forecasts help with capacity planning, resource allocation, or trend analysis when the underlying data is complex and exceeds what inline MetricsQL queries, including [predict_linear](https://docs.victoriametrics.com/victoriametrics/metricsql/#predict_linear), can handle.
> [!WARNING]
> However, please note that this mode should be used with care, as the model will produce `yhat_{h}` (and probably `yhat_lower_{h}`, and `yhat_upper_{h}`) time series **for each timeseries returned by input queries and for each forecasting horizon specified in `forecast_at` argument, which can lead to a significant increase in the number of active timeseries in VictoriaMetrics TSDB**.
Here's an example of how to produce forecasts using `vmanomaly` and combine it with the regular model, e.g. to estimate daily outcomes for a disk usage metric:
@@ -264,12 +267,12 @@ Here's an example of how to produce forecasts using `vmanomaly` and combine it w
schedulers:
periodic_5m: # this scheduler will be used to produce anomaly scores each 5 minutes using "regular" simple model
class: 'periodic'
fit_every: '100w'
fit_every: '1000d'
fit_window: '3d'
infer_every: '5m'
periodic_forecast: # this scheduler will be used to produce forecasts each 24h using "daily" model
class: 'periodic'
fit_every: '1000w'
fit_every: '1000d'
fit_window: '730d' # to fit the model on 2 years of data to account for seasonality and holidays
infer_every: '24h'
# https://docs.victoriametrics.com/anomaly-detection/components/reader/#vm-reader
@@ -289,6 +292,7 @@ reader:
1h
)
data_range: [0, 1]
detection_direction: 'above_expected' # query-level from v1.30.2
# step: '1m' # default will be inherited from sampling_period
disk_usage_perc_1d:
expr: |
@@ -300,14 +304,15 @@ reader:
)
step: '1d' # override default step to 1d, as we want to produce daily forecasts
data_range: [0, 1]
detection_direction: 'above_expected' # query-level from v1.30.2
# https://docs.victoriametrics.com/anomaly-detection/components/models/
models:
quantile_5m:
class: 'quantile_online' # online model, which updates itself each infer call
queries: ['disk_usage_perc_5m']
schedulers: ['periodic_5m']
decay: 0.99 # give more weight to recent data while using the bootstrap-only fit schedule
clip_predictions: True
detection_direction: 'above_expected' # as we are interested in spikes in capacity planning
quantiles: [0.25, 0.5, 0.75] # to produce median and upper quartiles
iqr_threshold: 2.0
@@ -315,8 +320,9 @@ models:
class: 'temporal_envelope'
queries: ['disk_usage_perc_1d']
schedulers: ['periodic_forecast']
alpha: 0.005 # capture the changes faster if increased
loss_reactivity: 3 # allow new deviations to update the envelope
clip_predictions: True
detection_direction: 'above_expected' # as we are interested in spikes in capacity planning
forecast_at: ['3d', '7d'] # this will produce forecasts for 3 and 7 days ahead
provide_series: ['yhat', 'yhat_upper'] # to write forecasts back to VictoriaMetrics, omitting `yhat_lower` as it is not needed in this example
seasonalities: [dow_smooth]
@@ -425,13 +431,15 @@ For information on migrating between different versions of `vmanomaly`, please r
> {{% available_from "v1.24.0" anomaly %}} This feature is best used in conjunction with [stateful mode](https://docs.victoriametrics.com/anomaly-detection/components/settings/#state-restoration) to ensure that the model state is preserved across service restarts.
> {{% available_from "v1.30.2" anomaly %}} Scheduler-managed fit data is **temporary**. It is removed after every dependent univariate or multivariate model completes fitting and commits its state, rather than being retained until the next `fit_every` cycle. Model dumps and state metadata remain available for restoration.
Here's an example of how to set it up in docker-compose using volumes:
```yaml
services:
# ...
vmanomaly:
container_name: vmanomaly
image: victoriametrics/vmanomaly:v1.30.1
image: victoriametrics/vmanomaly:v1.30.2
# ...
restart: always
volumes:
@@ -502,7 +510,7 @@ settings:
schedulers:
periodic:
class: 'periodic'
fit_every: '180d' # we need only initial fit to start
fit_every: '1000d'
fit_window: '4h' # reduced window, especially if the data doesn't have strong seasonality
infer_every: '1m' # the model will be updated during each infer call
# other schedulers ...
@@ -510,7 +518,7 @@ models:
zscore_example:
class: 'zscore_online'
min_n_samples_seen: 120 # i.e. minimal relevant seasonality or (initial) fit_window / sampling_period
decay: 0.999 # decay factor to control how fast the model adapts to new data, the lower, the faster it adapts
decay: 0.99 # decay factor to control how fast the model adapts to new data, the lower, the faster it adapts
schedulers: ['periodic']
# other model params ...
# other config sections ...
@@ -524,11 +532,11 @@ As a result, switching from the offline Z-score model to the Online Z-score mode
**New configuration**:
- `fit_window`: 4 hours
- `fit_every`: 180 days ( >1 week)
- `fit_every`: 1000 days ( >1 week)
The old configuration would perform 168 (hours in a week) `fit` calls, each using 2 days (48 hours) of data, totaling 168 * 48 = 8064 hours of data for each timeseries returned.
The new configuration performs only 1 `fit` call in 180 days, using 4 hours of data initially, totaling 4 hours of data, which is **magnitudes smaller**.
The new configuration performs only 1 `fit` call in 1000 days, using 4 hours of data initially, totaling 4 hours of data, which is **magnitudes smaller**.
P.s. `infer` data volume will remain the same for both models, so it does not affect the overall calculations.
@@ -556,9 +564,7 @@ models:
temporal_envelope:
class: temporal_envelope
# other model args
queries: [
'sum_alerts',
]
queries: ['sum_alerts']
# other config sections
```
@@ -578,9 +584,7 @@ models:
temporal_envelope:
class: temporal_envelope
# other model args
queries: [
'sum_alerts',
]
queries: ['sum_alerts']
# other config sections
```
@@ -598,10 +602,7 @@ models:
temporal_envelope:
class: temporal_envelope
# other model args
queries: [
'sum_alerts_pending',
'sum_alerts_firing',
]
queries: ['sum_alerts_pending', 'sum_alerts_firing']
# other config sections
```
@@ -651,10 +652,12 @@ options:
Minimum level to log. Default: INFO
```
For a side-by-side comparison of all split modes and their resulting sub-configurations, see [splitting strategies](https://docs.victoriametrics.com/anomaly-detection/scaling-vmanomaly/#splitting-strategies).
Heres an example of using the config splitter to divide configurations based on the `extra_filters` argument from the reader section:
```sh
docker pull victoriametrics/vmanomaly:v1.30.1 && docker image tag victoriametrics/vmanomaly:v1.30.1 vmanomaly
docker pull victoriametrics/vmanomaly:v1.30.2 && docker image tag victoriametrics/vmanomaly:v1.30.2 vmanomaly
```
```sh
@@ -687,10 +690,11 @@ reader:
# ...
queries:
extra_big_query: metricsql_expression_returning_too_many_timeseries
extra_filters:
extra_filters: [
# suppose you have a label `region` with values to deterministically define such subsets
- '{env="region_name_1"}'
'{env="region_name_1"}',
# ...
]
```
```yaml
@@ -700,10 +704,11 @@ reader:
# ...
queries:
extra_big_query: metricsql_expression_returning_too_many_timeseries
extra_filters:
extra_filters: [
# suppose you have a label `region` with values to deterministically define such subsets
- '{region="region_name_2"}'
'{region="region_name_2"}',
# ...
]
```
## Monitoring vmanomaly

View File

@@ -1,6 +1,7 @@
---
weight: 5
title: Migration
description: "Migration guide to the latest vmanomaly version."
menu:
docs:
identifier: "vmanomaly-migration"
@@ -45,7 +46,7 @@ There are 2 types of compatibility to consider when migrating in stateful mode:
| Group start | Group end | Compatibility | Notes |
|---------|--------- |------------|-------|
| [v1.29.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1291) | [v1.30.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1301) | Fully Compatible | v1.30.0 adds new [Temporal Envelope](https://docs.victoriametrics.com/anomaly-detection/components/models/#temporal-envelope) model state without changing the compatibility of existing model and data artifacts. v1.30.1 remains compatible with v1.30.0 state and its compatible predecessors. |
| [v1.29.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1291) | [v1.30.2](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1302) | Fully Compatible | v1.30.0 adds new [Temporal Envelope](https://docs.victoriametrics.com/anomaly-detection/components/models/#temporal-envelope) model state without changing the compatibility of existing model and data artifacts. v1.30.2 remains compatible with v1.30.1 state and its compatible predecessors; no persisted-state migration is required. |
| [v1.28.7](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1287) | [v1.29.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1290) | Partially compatible* | Dumped models of class [prophet](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet) and [seasonal quantile](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-seasonal-quantile) have problems with loading to [v1.29.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1290) due to dropped `pytz` library. **Upgrading directly from v1.28.7 to [v1.29.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1291) with a fix is suggested** |
| [v1.26.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1262) | [v1.28.7](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1287) | Fully Compatible | [v1.28.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1280) introduced [rolling](https://docs.victoriametrics.com/anomaly-detection/components/models/#rolling-models) model class drop in favor of [online](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-models) models (`rolling_quantile` and `std` models), however, it does not impact compatibility, as artifacts were not produced by default for rolling models. Also, offline `mad` and `zscore` models are redirecting to their respective online counterparts since [v1.28.4](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1284). |
| [v1.25.3](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1253) | [v1.26.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1270) | Partially Compatible* | [v1.25.3](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1253) introduced `forecast_at` argument for base [univariate](https://docs.victoriametrics.com/anomaly-detection/components/models/#univariate-models) and `Prophet` [models](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet), however, itself remains backward-reversible from newer states like [v1.26.2](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1262), [v1.27.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1270). (All models except `isolation_forest_multivariate` class will be dropped) |

View File

@@ -1,6 +1,7 @@
---
weight: 2
title: Presets
description: "Preconfigured anomaly detection configurations for widely-recognized metrics (e.g., node_exporter)"
menu:
docs:
parent: "anomaly-detection"

View File

@@ -1,6 +1,7 @@
---
weight: 1
title: Quick Start
description: "Get started with vmanomaly. Install, configure, and run anomaly detection."
menu:
docs:
parent: "anomaly-detection"
@@ -137,7 +138,7 @@ Below are the steps to get `vmanomaly` up and running inside a Docker container:
1. Pull Docker image:
```sh
docker pull victoriametrics/vmanomaly:v1.30.1
docker pull victoriametrics/vmanomaly:v1.30.2
```
2. Create the license file with your license key.
@@ -157,7 +158,7 @@ docker run -it \
-v ./license:/license \
-v ./config.yaml:/config.yaml \
-p 8490:8490 \
victoriametrics/vmanomaly:v1.30.1 \
victoriametrics/vmanomaly:v1.30.2 \
/config.yaml \
--licenseFile=/license \
--loggerLevel=INFO \
@@ -174,7 +175,7 @@ docker run -it \
-e VMANOMALY_DATA_DUMPS_DIR=/tmp/vmanomaly/data \
-e VMANOMALY_MODEL_DUMPS_DIR=/tmp/vmanomaly/models \
-p 8490:8490 \
victoriametrics/vmanomaly:v1.30.1 \
victoriametrics/vmanomaly:v1.30.2 \
/config.yaml \
--licenseFile=/license \
--loggerLevel=INFO \
@@ -187,7 +188,7 @@ services:
# ...
vmanomaly:
container_name: vmanomaly
image: victoriametrics/vmanomaly:v1.30.1
image: victoriametrics/vmanomaly:v1.30.2
# ...
restart: always
volumes:
@@ -250,12 +251,13 @@ Before deploying, check the correctness of your configuration validate config fi
### Example
Here is an example of a config file that runs the online [Temporal Envelope](https://docs.victoriametrics.com/anomaly-detection/components/models/#temporal-envelope) model on a CPU metric. The scheduler runs inference every five minutes and performs a full refit only every 100 weeks; between refits the model updates causally from each inference batch. The initial fit uses four weeks of data. The model produces `anomaly_score`, `yhat`, `yhat_lower`, and `yhat_upper` [series](https://docs.victoriametrics.com/anomaly-detection/components/models/#vmanomaly-output) for debugging, and its hour-of-day and day-of-week profiles follow the query timezone and daylight-saving-time changes.
Here is an example of a config file that runs the online [Temporal Envelope](https://docs.victoriametrics.com/anomaly-detection/components/models/#temporal-envelope) model on a CPU metric. The scheduler runs inference every five minutes and uses the fit only for initial bootstrap; between fits the model updates causally from each inference batch. The initial fit uses four weeks of data. The model produces `anomaly_score`, `yhat`, `yhat_lower`, and `yhat_upper` [series](https://docs.victoriametrics.com/anomaly-detection/components/models/#vmanomaly-output) for debugging, and its hour-of-day and day-of-week profiles follow the query timezone and daylight-saving-time changes.
```yaml
settings:
# https://docs.victoriametrics.com/anomaly-detection/components/settings/
n_workers: 2 # number of workers to run workload in parallel, set to 0 or negative number to use all available CPU cores
native_threads_per_worker: 0 # automatically divide container-aware CPU capacity across workers
anomaly_score_outside_data_range: 5.0 # default anomaly score for anomalies outside expected data range
restore_state: true # restore state from previous run, available since v1.24.0
# https://docs.victoriametrics.com/anomaly-detection/components/settings/#logger-levels
@@ -268,13 +270,12 @@ settings:
model.online.temporal_envelope: WARNING
schedulers:
100w_5m:
online_5m:
# https://docs.victoriametrics.com/anomaly-detection/components/scheduler/#periodic-scheduler
class: 'periodic'
infer_every: '5m'
scatter_infer_jobs: true
# Temporal Envelope learns online between full refits.
fit_every: '100w'
fit_every: '1000d'
fit_window: '4w'
models:
@@ -282,7 +283,7 @@ models:
temporal_envelope_model:
class: 'temporal_envelope'
queries: ['cpu_user']
schedulers: ['100w_5m']
schedulers: ['online_5m']
provide_series: ['anomaly_score', 'yhat', 'yhat_lower', 'yhat_upper'] # for debugging
seasonalities: ['hod_smooth', 'dow_smooth']
alpha: 0.005 # trend reactivity; try 0.0025-0.02
@@ -297,12 +298,15 @@ reader:
tenant_id: '0:0'
sampling_period: "5m"
tz: 'UTC' # set the IANA timezone that defines local calendar patterns, e.g. 'America/New_York'
workers: 0 # automatically choose bounded datasource concurrency
series_processing_batch_size: 8 # number of time series to process together while preparing data for fit or infer stages
queries:
# define your queries with MetricsQL - https://docs.victoriametrics.com/victoriametrics/metricsql/
cpu_user:
expr: 'sum(rate(node_cpu_seconds_total{mode=~"user"}[10m])) by (container)'
max_datapoints_per_query: 15000 # to deal with longer queries hitting search.MaxPointsPerTimeseries
data_range: [0, 'inf'] # query-level business policy from v1.30.2
detection_direction: 'above_expected' # query-level from v1.30.2; only spikes are anomalous
max_points_per_query: 15000 # to deal with longer queries hitting search.maxPointsPerTimeseries
# other queries ...
writer:

View File

@@ -1,6 +1,7 @@
---
weight: 3
title: Scaling vmanomaly
description: "High availability and horizontal scaling for vmanomaly."
menu:
docs:
identifier: "vmanomaly-scaling"
@@ -32,14 +33,15 @@ schedulers:
periodic_1d: # alias
class: 'periodic' # scheduler class
infer_every: "30s"
fit_every: "1h"
fit_every: "1000d"
fit_window: "24h"
# https://docs.victoriametrics.com/anomaly-detection/components/models/
models:
zscore: # we can set up alias for model
class: 'zscore' # model class
class: 'zscore_online' # online model class
z_threshold: 3.5
decay: 0.99 # give more weight to recent data while using the bootstrap-only fit schedule
queries: ['cpu_seconds_total', 'host_network_receive_errors']
# https://docs.victoriametrics.com/anomaly-detection/components/reader/#vm-reader
@@ -80,6 +82,7 @@ Additionally, a replication factor `R ≥ 1` ensures [high availability](#high-a
{{% content "vmanomaly-sharding-ha-diagram.md" %}}
> [!WARNING]
> Please [refer to deployment options section](#deployment-options) for the examples (Docker, Docker Compose, Helm). To avoid duplicate metrics being reported from each vmanomaly service used in sharded mode, make sure that [deduplication](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#deduplication) is configured on vmsingle or vmselect and vmstorage for the VictoriaMetrics instance used in the [writer section of the configuration](https://docs.victoriametrics.com/anomaly-detection/components/writer/).
Sharding configuration can be controlled by using the following environment variables:
@@ -87,7 +90,94 @@ Sharding configuration can be controlled by using the following environment vari
- **`VMANOMALY_MEMBERS_COUNT`**: Defines the total number of shards (i.e., available nodes to distribute [sub-configurations](#sub-configuration) to). <br>Defaults to `1` for backward compatibility.
- **`VMANOMALY_MEMBER_NUM`**: Specifies the shard index (`0` to `VMANOMALY_MEMBERS_COUNT - 1`), determining the subset of [sub-configurations](#sub-configuration) to run on a specific node. Defaults to `0`. Supports automatic **pod name discovery** in Kubernetes [StatefulSets](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/) (e.g., if set to `vmanomaly-node-exporter-7`, shard `7` will be extracted).
- **`VMANOMALY_REPLICATION_FACTOR`**: If `R > 1`, enables [high availability](#high-availability) by ensuring each [sub-configuration](#sub-configuration) is assigned to exactly `R` shards. Defaults to `1` (no replication).
- **`VMANOMALY_SPLIT_BY`**: Defines the logical entity used to split the global config into [sub-configurations](#sub-configuration). Defaults to `complete`, which provides the most granular distribution (1 model per [sub-config](#sub-configuration), mapped to 1 query and attached to 1 scheduler) for balanced workloads.
- **`VMANOMALY_SPLIT_BY`**: Defines the logical entity used to split the global config into [sub-configurations](#sub-configuration). The accepted values are `SCHEDULERS`, `MODELS`, `QUERIES`, `EXTRA_FILTERS`, and `COMPLETE` (case-insensitive). It defaults to `COMPLETE`, which usually provides the most granular and balanced distribution.
The split strategies differ as follows:
| `VMANOMALY_SPLIT_BY` | Unit of work in each sub-configuration | Recommended use |
| --- | --- | --- |
| `SCHEDULERS` | One scheduler and the workload attached to it | Separate workloads by fit and inference cadence. The number of sub-configurations is limited by the number of referenced schedulers. |
| `MODELS` | One configured model alias with its attached schedulers and queries | Isolate computationally different models or distribute several models that process the same queries. |
| `QUERIES` | One query for [univariate models](https://docs.victoriametrics.com/anomaly-detection/components/models/#univariate-models); the complete attached query set for each [multivariate model](https://docs.victoriametrics.com/anomaly-detection/components/models/#multivariate-models) | Distribute independent query workloads. Queries belonging to one multivariate model remain together because the model needs all channels. This option does not split the series returned by one query. |
| `EXTRA_FILTERS` | One configured `reader.extra_filters` selector, with the full model/query/scheduler topology retained | Partition the series returned by large queries, for example by region, cluster, another stable label, or by [VictoriaMetrics tenant](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#multitenancy-via-labels) using `vm_account_id` and `vm_project_id` selectors with the multitenant endpoint. The filters must already be defined in the global configuration. |
| `COMPLETE` | One valid scheduler/model/query combination; [multivariate](https://docs.victoriametrics.com/anomaly-detection/components/models/#multivariate-models) query sets remain together | Obtain the finest general-purpose split and the default choice for balanced sharding. `reader.extra_filters` are intentionally not expanded by this strategy. |
After the selected strategy creates the sub-configurations, they are assigned to members in deterministic round-robin order and then replicated according to `VMANOMALY_REPLICATION_FACTOR`.
### Splitting strategies
{{% collapse name="Configuration and resulting sub-configurations" %}}
The following abbreviated global configuration contains two schedulers, two models, four queries, and two data partitions:
```yaml
schedulers:
fast:
class: periodic
infer_every: 1m
fit_every: 1000d
fit_window: 1d
seasonal:
class: periodic
infer_every: 5m
fit_every: 1000d
fit_window: 2w
models:
cpu_zscore:
class: zscore_online
schedulers: [fast]
queries: [cpu, error_rate]
decay: 0.99
gpu_envelope:
class: temporal_envelope_multivariate
schedulers: [seasonal]
queries: [temperature, power]
seasonalities: [hod_smooth, dow_smooth]
reader:
class: vm
datasource_url: http://victoriametrics:8428/
sampling_period: 1m
queries:
cpu:
expr: avg(rate(node_cpu_seconds_total[5m])) by (instance)
error_rate:
expr: rate(application_errors_total[5m])
temperature:
expr: avg(gpu_temperature_celsius) by (gpu)
power:
expr: avg(gpu_power_watts) by (gpu)
extra_filters: ['{region="us-east"}', '{region="eu-west"}']
writer:
class: vm
datasource_url: http://victoriametrics:8428/
```
For this configuration, each strategy produces the following logical units before they are assigned to shards:
| Value | Resulting sub-configurations |
| --- | --- |
| `SCHEDULERS` | `fast`; `seasonal` |
| `MODELS` | `cpu_zscore`; `gpu_envelope` |
| `QUERIES` | `cpu`; `error_rate`; the multivariate set `power,temperature` |
| `EXTRA_FILTERS` | `{region="us-east"}`; `{region="eu-west"}`; each retains all schedulers, models, and queries, while the query context is restricted by its selector |
| `COMPLETE` | `fast:cpu_zscore:cpu`; `fast:cpu_zscore:error_rate`; `seasonal:gpu_envelope:power,temperature` |
For example, choose the query split with:
```yaml
environment:
VMANOMALY_MEMBERS_COUNT: 3
VMANOMALY_MEMBER_NUM: 0
VMANOMALY_REPLICATION_FACTOR: 1
VMANOMALY_SPLIT_BY: QUERIES
```
To partition the timeseries returned by the same large query instead, define non-overlapping selectors in `reader.extra_filters` and use `VMANOMALY_SPLIT_BY: EXTRA_FILTERS`. Each generated sub-configuration keeps one selector, for example `{region="us-east"}` or `{region="eu-west"}`.
{{% /collapse %}}
---
@@ -130,6 +220,7 @@ When `VMANOMALY_REPLICATION_FACTOR` > 1, each [sub-config](#sub-configuration) `
{{% content "vmanomaly-sharding-ha-diagram.md" %}}
> [!WARNING]
> Please [refer to deployment options section](#deployment-options) for the examples (Docker, Docker Compose, Helm). To avoid duplicate metrics being reported from each vmanomaly service used in sharded mode, make sure that [deduplication](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#deduplication) is configured on vmsingle or vmselect and vmstorage for the VictoriaMetrics instance used in the [writer section of the configuration](https://docs.victoriametrics.com/anomaly-detection/components/writer/).
### Example
@@ -198,7 +289,11 @@ services:
user: "1000:1000"
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:8490/health"]
test:
- "CMD"
- "curl"
- "-f"
- "http://127.0.0.1:8490/health"
interval: 30s
timeout: 10s
retries: 5
@@ -218,7 +313,11 @@ services:
user: "1000:1000"
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:8490/health"]
test:
- "CMD"
- "curl"
- "-f"
- "http://127.0.0.1:8490/health"
interval: 30s
timeout: 10s
retries: 5

View File

@@ -1,6 +1,7 @@
---
weight: 4
title: Self-monitoring
description: "Track vmanomaly health and operational performance."
menu:
docs:
identifier: "vmanomaly-self-monitoring"

View File

@@ -1,6 +1,7 @@
---
weight: 2
title: UI
description: "Built-in vmui-like UI for exploring anomaly detection results."
menu:
docs:
parent: "anomaly-detection"
@@ -137,13 +138,13 @@ users:
password: '<password>'
url_map:
- src_hosts:
- "metrics.local.some-domain.net"
- "metrics.local.some-domain.net"
url_prefix: "http://victoriametrics:8428"
- src_hosts:
- "vl.local.some-domain.net"
- "vl.local.some-domain.net"
url_prefix: "http://victorialogs:9428"
- src_hosts:
- "vmanomaly.local.some-domain.net"
- "vmanomaly.local.some-domain.net"
url_prefix: "http://vmanomaly:8490"
keep_original_host: true
```
@@ -316,7 +317,7 @@ docker run -it --rm \
-e VMANOMALY_MCP_SERVER_URL=http://mcp-vmanomaly:8081/mcp \
-p 8080:8080 \
-p 8490:8490 \
victoriametrics/vmanomaly:v1.30.1 \
victoriametrics/vmanomaly:v1.30.2 \
vmanomaly_config.yaml
```
@@ -645,6 +646,13 @@ If the **results** look good and the **model configuration should be deployed in
{{% collapse name="Release history" %}}
### v1.8.2
Released: 2026-08-13
vmanomaly version: [v1.30.2](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1302)
- BUGFIX: Fixed tenant discovery for VictoriaMetrics datasource URLs containing `/select/multitenant/prometheus`. The UI now loads available numeric tenants from `/admin/tenants` and can switch the datasource URL from `multitenant` to the selected tenant.
### v1.8.1
Released: 2026-08-06

View File

@@ -1,5 +1,6 @@
---
title: Anomaly Detection
description: "Use vmanomaly to detect anomalies in metrics and logs. Configure models, run inference, monitor the service, and connect results to alerts and dashboards."
weight: 50
menu:
docs:

View File

@@ -37,6 +37,7 @@ The following minimal configuration demonstrates current many-to-many model, que
```yaml
settings:
n_workers: 4 # number of workers to run models in parallel
native_threads_per_worker: 0 # automatically divide container-aware CPU capacity across workers
anomaly_score_outside_data_range: 5.0 # default anomaly score for anomalies outside expected data range
restore_state: True # restore state from previous run, if available
retention: # how long to keep stale models on disk/in memory
@@ -50,15 +51,15 @@ schedulers:
class: 'periodic' # scheduler class
infer_every: "30s" # how often to produce anomaly scores for new data
scatter_infer_jobs: true # distribute infer jobs evenly across the infer interval to reduce synchronized bursts
fit_every: "365d" # how often to re-fit the models, for online models used effectively once, then they are updated with new data and won't require re-fit
fit_every: "1000d" # bootstrap-only schedule; use a finite cadence if accumulated state must be reset
fit_window: "3d" # how much historical data to use for fit stage
start_from: "00:00" # align the annual fit schedule to midnight in the configured timezone
start_from: "00:00" # align the bootstrap fit to midnight in the configured timezone
tz: "Europe/Kyiv" # timezone to use for start_from
periodic_online_weekly:
class: 'periodic'
infer_every: "15m"
scatter_infer_jobs: true
fit_every: "365d" # online state continues adapting between infrequent full re-fits
fit_every: "1000d" # bootstrap-only schedule; use a finite cadence if accumulated state must be reset
fit_window: "14d"
# if no start_from is specified, jobs will start immediately after service starts
@@ -72,17 +73,15 @@ models:
provide_series: ['anomaly_score', 'y', 'yhat', 'yhat_upper'] # what series to produce as output of the model
queries: ['host_network_receive_errors'] # what queries to run particular model on
schedulers: ['periodic_online'] # will be fit once, used for infer every 30s
min_dev_from_expected: 0.0 # turned off. if |y - yhat| < min_dev_from_expected, anomaly score will be 0
detection_direction: 'above_expected' # detect anomalies only when y > yhat, "peaks"
clip_predictions: True # clip predictions to expected data range, i.e. [0, inf] for this query `host_network_receive_errors
envelope_weekly: # we can set up alias for model
class: 'temporal_envelope'
alpha: 0.005 # adapt the trend while using the bootstrap-only fit schedule
loss_reactivity: 3 # allow new deviations to update the envelope
provide_series: ['anomaly_score', 'y', 'yhat', 'yhat_lower', 'yhat_upper']
queries: ['cpu_seconds_total']
schedulers: ['periodic_online_weekly'] # fit on two weekly cycles, then update online every 15m
min_dev_from_expected: [0.01, 0.01] # minimum deviation from expected value to be even considered as anomaly
anomaly_score_outside_data_range: 1.5 # override default anomaly score outside expected data range
detection_direction: 'above_expected'
clip_predictions: True # clip predictions to expected data range, i.e. [0, inf] for this query `cpu_seconds_total`
seasonalities: ['hod_smooth', 'dow_smooth']
@@ -93,6 +92,7 @@ reader:
datasource_url: "https://play.victoriametrics.com/"
tenant_id: "0:0"
sampling_period: "30s" # what data resolution to fetch from VictoriaMetrics' /query_range endpoint
workers: 0 # automatically choose bounded datasource concurrency
latency_offset: '1ms'
query_from_last_seen_timestamp: False
tz: "UTC" # timezone to use for queries without explicit timezone
@@ -101,11 +101,15 @@ reader:
cpu_seconds_total:
expr: 'avg(rate(node_cpu_seconds_total[5m])) by (mode)'
# step: '30s' # if not set, will be equal to reader-level sampling_period
data_range: [0, 'inf'] # expected value range, anomaly_score = anomaly_score_outside_data_range if y (real value) is outside
data_range: [0, 'inf'] # query-level business policy from v1.30.2
detection_direction: 'above_expected' # query-level from v1.30.2; detect spikes only
min_dev_from_expected: [0.01, 0.01] # query-level from v1.30.2
host_network_receive_errors:
expr: 'rate(node_network_receive_errs_total[3m]) / rate(node_network_receive_packets_total[3m])'
step: '15m' # here we override per-query `sampling_period` to request way less data from VM TSDB
data_range: [0, 'inf']
data_range: [0, 'inf'] # query-level business policy from v1.30.2
detection_direction: 'above_expected' # query-level from v1.30.2; detect spikes only
min_dev_from_expected: 0.0 # query-level from v1.30.2; absolute-deviation filtering is disabled
# where to write data to
# https://docs.victoriametrics.com/anomaly-detection/components/writer/
@@ -146,7 +150,7 @@ server:
{{% available_from "v1.25.0" anomaly %}} The service supports hot reload of configuration files, applying changes without an explicit restart. Enable it with the `--watch` [CLI argument](https://docs.victoriametrics.com/anomaly-detection/quickstart/#command-line-arguments). The `vmanomaly_config_reload_enabled` [self-monitoring metric](https://docs.victoriametrics.com/anomaly-detection/components/monitoring/#startup-metrics) is `1` when hot reload is enabled and `0` otherwise.
> [!NOTE]
> [!WARNING]
> {{% deprecated_from "v1.29.5" anomaly %}} File system event-based hot reload has been deprecated in favor of content-based polling with configurable `-configCheckInterval` due to reliability issues with Kubernetes ConfigMap symlink rotations and other filesystems where event delivery can be inconsistent. If you were using file system event-based hot reload, please switch to content-based polling by enabling `--watch` flag and configuring `-configCheckInterval` as needed.
### How it works
@@ -173,7 +177,7 @@ schedulers:
periodic:
class: 'periodic'
infer_every: "30s"
fit_every: "365d"
fit_every: "1000d" # bootstrap-only schedule; use a finite cadence if accumulated state must be reset
fit_window: "24h"
reader:

View File

@@ -1,5 +1,6 @@
---
title: Components
description: "Architecture overview. Models, reader, writer, scheduler, monitoring, settings, server."
weight: 3
menu:
docs:

View File

@@ -1,5 +1,6 @@
---
title: Models
description: "Model types and configuration. Built-in and custom anomaly detection models."
weight: 1
menu:
docs:
@@ -65,6 +66,9 @@ models:
Common arguments supported by every model were introduced in [v1.10.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1100).
> [!WARNING]
> Configuring `data_range`, `detection_direction`, `min_dev_from_expected`, or `min_rel_dev_from_expected` at model level is deprecated {{% deprecated_from "v1.30.2" anomaly %}}. These stable KPI policies belong under [`reader.queries.<alias>`](https://docs.victoriametrics.com/anomaly-detection/components/reader/#per-query-parameters), where they remain consistent across every [univariate](#univariate-models) or [multivariate](#multivariate-models) model that uses the query. Existing model-level values remain compatible as model-local fallbacks when an attached query does not define the corresponding field; an explicit query value is authoritative.
<div class="collapse-group">
{{% collapse name="Queries" %}}
@@ -145,61 +149,46 @@ models:
{{% collapse name="Detection direction" %}}
### Detection direction
The `detection_direction` argument{{% available_from "v1.13.0" anomaly %}} can reduce [false positives](https://victoriametrics.com/blog/victoriametrics-anomaly-detection-handbook-chapter-1/#false-positive) when domain knowledge indicates that only values above or below the expected value are anomalous. Available values are `both`, `above_expected`, and `below_expected`.
The `detection_direction` argument{{% available_from "v1.13.0" anomaly %}} can reduce [false positives](https://victoriametrics.com/blog/victoriametrics-anomaly-detection-handbook-chapter-1/#false-positive) when domain knowledge indicates that only values above or below the expected value are anomalous. Available values are `both`, `above_expected`, and `below_expected`. Configure it on the input query; model-level placement is {{% deprecated_from "v1.30.2" anomaly %}}.
Here's how default (backward-compatible) behavior looks like - anomalies will be tracked in `both` directions (`y > yhat` or `y < yhat`). This is useful when there is no domain expertise to filter the required direction.
Here's how the three options differ:
![schema_detection_direction=both](schema_detection_direction_both.webp)
![detection_direction comparison](schema_detection_direction.webp)
With the default, backward-compatible `both` value, anomalies are tracked in both directions (`y > yhat` or `y < yhat`). This is useful when there is no domain expertise to filter the required direction.
When set to `above_expected`, anomalies are tracked only when `y > yhat`.
*Example metrics*: Error rate, response time, page load time, number of failed transactions - metrics where *lower values are better*, so **higher** values are typically tracked.
![schema_detection_direction=above_expected](schema_detection_direction_above_expected.webp)
When set to `below_expected`, anomalies are tracked only when `y < yhat`.
*Example metrics*: Service Level Agreement (SLA) compliance, conversion rate, Customer Satisfaction Score (CSAT) - metrics where *higher values are better*, so **lower** values are typically tracked.
![schema_detection_direction=below_expected](schema_detection_direction_below_expected.webp)
Config with a split example:
One model can use multiple queries with different directions because the policy belongs to each query:
```yaml
models:
model_above_expected:
class: 'zscore_online'
z_threshold: 3.0
# track only cases when y > yhat, otherwise anomaly_score would be explicitly set to 0
detection_direction: 'above_expected'
# for this query we do not need to track lower values, thus, set anomaly detection tracking for y > yhat (above_expected)
queries: ['query_values_the_lower_the_better']
model_below_expected:
class: 'zscore_online'
z_threshold: 3.0
# track only cases when y < yhat, otherwise anomaly_score would be explicitly set to 0
detection_direction: 'below_expected'
# for this query we do not need to track higher values, thus, set anomaly detection tracking for y < yhat (above_expected)
queries: ['query_values_the_higher_the_better']
model_bidirectional_default:
class: 'zscore_online'
z_threshold: 3.0
# track in both direction, same backward-compatible behavior in case this arg is missing
detection_direction: 'both'
# for this query both directions can be equally important for anomaly detection, thus, setting it bidirectional (both)
queries: ['query_values_both_direction_matters']
reader:
# ...
queries:
query_values_the_lower_the_better:
query_values_the_lower_the_better:
expr: metricsql_expression1
query_values_the_higher_the_better:
detection_direction: 'above_expected' # query-level from v1.30.2; only y > yhat can be anomalous
query_values_the_higher_the_better:
expr: metricsql_expression2
query_values_both_direction_matters:
detection_direction: 'below_expected' # query-level from v1.30.2; only y < yhat can be anomalous
query_values_both_direction_matters:
expr: metricsql_expression3
detection_direction: 'both' # query-level from v1.30.2; the default when omitted
models:
model_all_directions:
class: 'zscore_online'
z_threshold: 3.0
queries: [
'query_values_the_lower_the_better',
'query_values_the_higher_the_better',
'query_values_both_direction_matters',
]
# other components like writer, schedule, monitoring
```
@@ -209,7 +198,7 @@ reader:
### Minimal deviation from expected
`min_dev_from_expected`{{% available_from "v1.13.0" anomaly %}} argument is designed to **reduce [false positives](https://victoriametrics.com/blog/victoriametrics-anomaly-detection-handbook-chapter-1/#false-positive)** in scenarios where deviations between the actual value (`y`) and the expected value (`yhat`) are **relatively** high. Such deviations can cause models to generate high [anomaly scores](https://docs.victoriametrics.com/anomaly-detection/faq/#what-is-anomaly-score). However, these deviations may not be significant enough in **absolute values** from a business perspective to be considered anomalies. This parameter ensures that anomaly scores for data points where `|y - yhat| < min_dev_from_expected` are explicitly set to 0. By default, if this parameter is not set, it is set to `0` to maintain backward compatibility.
`min_dev_from_expected`{{% available_from "v1.13.0" anomaly %}} argument is designed to **reduce [false positives](https://victoriametrics.com/blog/victoriametrics-anomaly-detection-handbook-chapter-1/#false-positive)** in scenarios where deviations between the actual value (`y`) and the expected value (`yhat`) are **relatively** high. Such deviations can cause models to generate high [anomaly scores](https://docs.victoriametrics.com/anomaly-detection/faq/#what-is-anomaly-score). However, these deviations may not be significant enough in **absolute values** from a business perspective to be considered anomalies. This parameter ensures that anomaly scores for data points where `|y - yhat| < min_dev_from_expected` are explicitly set to 0. By default, if this parameter is not set, it is set to `0` to maintain backward compatibility. Configure it on the input query; model-level placement is {{% deprecated_from "v1.30.2" anomaly %}}.
> [!NOTE]
{{% available_from "v1.23.0" anomaly %}} The `min_dev_from_expected` argument can be a list of two float values, allowing separate thresholds for upper and lower deviations. This is useful when the acceptable deviation varies in different directions (e.g., `min_dev_from_expected: [0.01, 0.02]` means that the lower bound is `0.01` when `y` is less than `yhat` and the upper bound is `0.02` when `y` is greater than `yhat`). If only one value is provided, it is broadcasted to both directions, meaning that the same threshold is applied for both upper and lower deviations (e.g., `min_dev_from_expected: 0.01` means that the lower bound is `0.01` when `y` is less than `yhat` and the upper bound is also `0.01` when `y` is greater than `yhat`).
@@ -218,15 +207,9 @@ reader:
*Example*: Consider a scenario where CPU utilization in specific mode is low and oscillates around 0.3% (0.003). A sudden spike to 1.3% (0.013) represents a +333% increase in **relative** terms, but only a +1 percentage point (0.01) increase in **absolute** terms, which may be negligible and not warrant an alert. Setting the `min_dev_from_expected` argument to `0.01` (1%) will ensure that all anomaly scores for deviations <= `0.01` are set to 0.
Visualizations below demonstrate this concept; the green zone defined as the `[yhat - min_dev_from_expected, yhat + min_dev_from_expected]` range excludes actual data points (`y`) from generating anomaly scores if they fall within that range.
The visualization below demonstrates this concept. The narrow blue model prediction boundary is nested inside the wider green business protection boundary. Actual values outside the prediction boundary but still within `[yhat - min_dev_from_expected, yhat + min_dev_from_expected]` receive `anomaly_score = 0`; only values outside the green boundary remain anomalous.
![min_dev_from_expected-default](schema_min_dev_from_expected_0.webp)
![min_dev_from_expected-small](schema_min_dev_from_expected_1_0.webp)
![min_dev_from_expected-big](schema_min_dev_from_expected_5_0.webp)
![min_dev_from_expected](schema_min_dev_from_expected.webp)
Example config of how to use this param based on query results:
@@ -236,23 +219,17 @@ reader:
# ...
queries:
# the usage of min_dev should reduce false positives here
need_to_include_min_dev:
need_to_include_min_dev:
expr: small_abs_values_metricsql_expression
min_dev_from_expected: [5.0, 5.0] # query-level from v1.30.2
# min_dev is not really needed here
normal_behavior:
normal_behavior:
expr: no_need_to_exclude_small_deviations_metricsql_expression
models:
zscore_with_min_dev:
zscore:
class: 'zscore_online'
z_threshold: 3
min_dev_from_expected: [5.0, 5.0] # set the same threshold for both directions, meaning that deviations less than 5.0 in absolute values won't be considered anomalous, even if they are relatively significant
queries: ['need_to_include_min_dev'] # use such models on queries where domain experience confirm usefulness
zscore_wo_min_dev:
class: 'zscore_online'
z_threshold: 3
# if not set, equals to setting min_dev_from_expected == 0 (meaning no filtering is applied)
# min_dev_from_expected: [0.0, 0.0]
queries: ['normal_behavior'] # use the default where it's not needed
queries: ['need_to_include_min_dev', 'normal_behavior']
```
{{% /collapse %}}
@@ -261,13 +238,17 @@ models:
### Minimal relative deviation from expected
{{% available_from "v1.29.1" anomaly %}} `min_rel_dev_from_expected` argument serves a similar purpose to `min_dev_from_expected` (see [section above](#minimal-deviation-from-expected)), but focuses on **relative deviations** rather than absolute ones. It is designed to reduce [false positives](https://victoriametrics.com/blog/victoriametrics-anomaly-detection-handbook-chapter-1/#false-positive) in scenarios where the relative deviation between the actual value (`y`) and the expected value (`yhat`) is high, but the absolute deviation is not significant enough to be considered an anomaly from a business perspective. This parameter ensures that anomaly scores for data points where `|y - yhat| / |yhat| < min_rel_dev_from_expected` are explicitly set to 0. By default, if this parameter is not set, it is set to `0` to maintain backward compatibility.
{{% available_from "v1.29.1" anomaly %}} `min_rel_dev_from_expected` argument serves a similar purpose to `min_dev_from_expected` (see [section above](#minimal-deviation-from-expected)), but focuses on **relative deviations** rather than absolute ones. It is designed to reduce [false positives](https://victoriametrics.com/blog/victoriametrics-anomaly-detection-handbook-chapter-1/#false-positive) in scenarios where the relative deviation between the actual value (`y`) and the expected value (`yhat`) is high, but the absolute deviation is not significant enough to be considered an anomaly from a business perspective. This parameter ensures that anomaly scores for data points where `|y - yhat| / |yhat| < min_rel_dev_from_expected` are explicitly set to 0. By default, if this parameter is not set, it is set to `0` to maintain backward compatibility. Configure it on the input query; model-level placement is {{% deprecated_from "v1.30.2" anomaly %}}.
Parameter can be a list of two float values, *allowing separate thresholds for upper and lower relative deviations*. If only one value is provided, it is broadcasted to both directions.
> [!NOTE]
If both `min_dev_from_expected` [arg](#minimal-deviation-from-expected) and `min_rel_dev_from_expected` are set, the model will combine both filters. A data point will be considered anomalous (i.e., have an anomaly score != 0) only if it exceeds **both** the *absolute* deviation threshold defined by `min_dev_from_expected` and the *relative* deviation threshold defined by `min_rel_dev_from_expected`. This allows for more granular control over anomaly detection, ensuring that only significant deviations in both absolute and relative terms are flagged as anomalies.
The green business protection boundary below scales with `|yhat|`, while the model prediction boundary remains visible inside it. Actual values outside the blue boundary but inside the proportional green boundary receive `anomaly_score = 0`.
![min_rel_dev_from_expected](schema_min_rel_dev_from_expected.webp)
*Example*: Consider a scenario of monitoring incoming traffic to websites that typically receives *unknown in advance* requests per second (from tens to thousands). Setting absolute deviation threshold with `min_dev_from_expected` *may not be effective in reducing false positives*, as even a small increase in traffic (e.g., from 10 to 20 requests per second) can represent a 100% relative increase, which may be significant for that website. Instead, setting `min_rel_dev_from_expected` to smaller relative value - `[20, 40]` (20/40%) - will ensure that traffic drop from 10 to 8 requests per second (20% decrease) and traffic spike from 10 to 14 requests per second (40% increase) won't be considered anomalous, even if they exceed confidence intervals, thus, reducing false positives for small absolute deviations that are relatively significant.
@@ -279,23 +260,17 @@ reader:
# ...
queries:
# the usage of min_rel_dev should reduce false positives here
need_to_include_min_rel_dev:
need_to_include_min_rel_dev:
expr: small_abs_values_metricsql_expression
min_rel_dev_from_expected: [10, 20] # query-level from v1.30.2
# min_rel_dev is not really needed here
normal_behavior:
normal_behavior:
expr: no_need_to_exclude_small_deviations_metricsql_expression
models:
zscore_with_min_rel_dev:
zscore:
class: 'zscore_online'
z_threshold: 3
min_rel_dev_from_expected: [10, 20] # set different thresholds for both directions, meaning that relative deviations less than 10% when y < yhat and less than 20% when y > yhat won't be considered anomalous, even if they exceed confidence intervals, thus, reducing false positives for small absolute deviations that are relatively significant
queries: ['need_to_include_min_rel_dev'] # use such models on queries where domain experience confirm usefulness
zscore_wo_min_rel_dev:
class: 'zscore_online'
z_threshold: 3
# if not set, equals to setting min_rel_dev_from_expected == 0 (meaning no filtering is applied)
# min_rel_dev_from_expected: [0, 0]
queries: ['normal_behavior'] # use the default where it's not needed
queries: ['need_to_include_min_rel_dev', 'normal_behavior']
```
@@ -318,17 +293,29 @@ reader:
# assume there are M unique hosts identified by the `host` label
queries:
# return one timeseries for each CPU mode per host, total = N*M timeseries
cpu: sum(rate(node_cpu_seconds_total[5m])) by (host, mode)
cpu:
expr: sum(rate(node_cpu_seconds_total[5m])) by (host, mode)
data_range: [0, 'inf']
detection_direction: both
min_rel_dev_from_expected: [15, 15]
# return one timeseries per host, total = 1*M timeseries
ram: |
(
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)
/ node_memory_MemTotal_bytes
) * 100 by (host)
ram:
expr: |
100 * (
1 - node_memory_MemAvailable_bytes
/ node_memory_MemTotal_bytes
)
data_range: [0, 100]
detection_direction: above_expected
min_rel_dev_from_expected: [0, 15]
# return one timeseries per host for both network receive and transmit data, total = 1*M timeseries
network: |
sum(rate(node_network_receive_bytes_total[5m])) by (host)
+ sum(rate(node_network_transmit_bytes_total[5m])) by (host)
network:
expr: |
sum(rate(node_network_receive_bytes_total[5m])) by (host)
+ sum(rate(node_network_transmit_bytes_total[5m])) by (host)
data_range: [0, 'inf']
detection_direction: below_expected
min_rel_dev_from_expected: [20, 0]
models:
envelope: # alias for the model
@@ -342,6 +329,9 @@ models:
groupby: [host]
```
> [!TIP]
> {{% available_from "v1.30.2" anomaly %}} Multivariate Temporal Envelope applies each query's [`data_range`, `detection_direction`, and minimum relative deviation](https://docs.victoriametrics.com/anomaly-detection/components/reader/#per-query-parameters) to every channel returned by that query before aggregating the joint anomaly score. The example detects CPU deviations in either direction, RAM increases of at least 15%, and network drops of at least 20% within each host model.
{{% /collapse %}}
{{% collapse name="Scale" %}}
@@ -359,6 +349,10 @@ For example, setting `scale: [1.2, 0.75]` for particular model will:
- **Increase** the width of the lower confidence interval by **20%**.
- **Decrease** the width of the upper confidence boundary by **25%**.
Alternative visualization:
![two-sided scale comparison](schema_scale_overview_v2.webp)
The most common **use case** is when there is a preference to **widen one side** to blacklist smaller false positives (which otherwise would have [anomaly scores](https://docs.victoriametrics.com/anomaly-detection/faq/#how-is-anomaly-score-calculated) **only slightly higher than 1.0**, still making such data points **anomalous**), while **tightening the other side** to avoid missing true positives due to an overly loose margin (leading to [anomaly scores](https://docs.victoriametrics.com/anomaly-detection/faq/#how-is-anomaly-score-calculated) being slightly less than 1.0, making such data points **non-anomalous**).
```yaml
@@ -557,6 +551,8 @@ For a multivariate model, **one shared model instance** is fitted and used acros
For example, if you have some **multivariate** model to use 3 [MetricQL queries](https://docs.victoriametrics.com/victoriametrics/metricsql/), each returning 5 time series, there will be one shared model created in total. Once fit, this model will expect **exactly 15 time series with exact same labelsets as an input**. This model will produce **one shared [output](#vmanomaly-output)**.
> {{% available_from "v1.30.2" anomaly %}} Multivariate Temporal Envelope and Isolation Forest accept matching input channels in any order. The channel set must still match the fitted model exactly: missing, extra, and duplicate channels are rejected, while a matching set is restored to learned fit order before inference or online updates.
> {{% available_from "v1.16.0" anomaly %}} N models — one for each N unique combinations of label values specified in the `groupby` [common argument](#group-by) — can be trained. This allows for context separation (e.g., one model per host, region, or other relevant grouping label), leading to improved accuracy and faster training. See an example [here](#group-by).
If during an inference, you got a **different amount of series** or some series having a **new labelset** (not present in any of fitted models), the inference will be skipped until you get a model, trained particularly for such labelset during forthcoming re-fit step.
@@ -685,7 +681,7 @@ Selecting model [hyperparameters](https://en.wikipedia.org/wiki/Hyperparameter_(
- `tuned_class_name` (string) - [Built-in model class](#built-in-models) to wrap, i.e. `zscore_online`
- `optimization_params` (dict) - Optimization parameters for *unsupervised* model tuning. Control percentage of found anomalies, as well as a tradeoff between time spent and the accuracy. The higher `timeout` and `n_trials` are, the better model configuration can be found for `tuned_class_name`, but the longer it takes and vice versa. Set `n_jobs` to `-1` to use all the CPUs available, it makes sense if only you have a big dataset to train on during `fit` calls, otherwise overhead isn't worth it.
- `anomaly_percentage` (float) - Expected percentage of anomalies that can be seen in training data, from `[0, 0.5)` interval (i.e. 0.01 means it's expected ~ 1% of anomalies to be present in training data). This is a *required* parameter.
- `optimized_business_params` (list[string]) - {{% available_from "v1.15.0" anomaly %}} this argument allows particular [business-specific parameters](#common-args) such as [`detection_direction`](https://docs.victoriametrics.com/anomaly-detection/components/models/#detection-direction) or [`min_dev_from_expected`](https://docs.victoriametrics.com/anomaly-detection/components/models/#minimal-deviation-from-expected) to remain **unchanged during optimizations, retaining their initial values**. I.e. setting `optimized_business_params` to `['detection_direction']` will allow to optimize only `detection_direction` business-specific arg, while `min_dev_from_expected` will retain its default value of (e.g. [1, 2] if set to that value in model config). By default and if not set, will be equal to `[]` (empty list), meaning no business params will be optimized. **A recommended option is to leave it empty** as this feature is still experimental and may lead to unexpected results.
- `optimized_business_params` (list[string]) - {{% available_from "v1.15.0" anomaly %}} Experimental optimization of model-level business parameters is {{% deprecated_from "v1.30.2" anomaly %}}. Keep this list empty and configure stable `detection_direction`, `min_dev_from_expected`, and `min_rel_dev_from_expected` policies on [`reader.queries.<alias>`](https://docs.victoriametrics.com/anomaly-detection/components/reader/#per-query-parameters) instead.
- `seed` (int) - Random seed for reproducibility and deterministic nature of underlying optimizations.
- `validation_scheme` (string) - {{% available_from "v1.25.1" anomaly %}} the validation scheme to use for hyperparameter tuning, either `regular` (time-based default) or `leaky` (regular cross-validation with `n_splits` folds, where each fold is a time-based split of the data). The `leaky` scheme is recommended for `anomaly_percentage` ~ 0%, as it allows the model to "see" all the datapoints at least once during the optimization process, which can lead to better results in such cases. Defaults to `regular`.
- `n_splits` (int) - How many folds to create for hyperparameter tuning out of your data. The higher, the longer it takes but the better the results can be. Defaults to 3.
@@ -809,7 +805,7 @@ For simple profiles without strong trend or seasonality, prefer [Online MAD](#on
Preset suffixes describe expected profile shape: `smooth` represents gradual recurring curves, `spiky` represents narrow phase peaks, and `plateau` represents sustained calendar levels. Choose only profiles supported by the data. Calendar and holiday features use civil time from the configured query timezone, so hour/day profiles remain aligned across daylight-saving-time transitions.
Temporal Envelope also supports the [common model arguments](#common-args), including `queries`, `schedulers`, `provide_series`, `detection_direction`, `scale`, `clip_predictions`, `min_dev_from_expected`, and `min_rel_dev_from_expected`. Input `data_range` and query timezone are configured on the [reader](https://docs.victoriametrics.com/anomaly-detection/components/reader/#config-parameters).
Temporal Envelope also supports the [common model arguments](#common-args), including `queries`, `schedulers`, `provide_series`, `scale`, and `clip_predictions`. Configure `data_range`, `detection_direction`, `min_dev_from_expected`, `min_rel_dev_from_expected`, and query timezone under the corresponding [reader query](https://docs.victoriametrics.com/anomaly-detection/components/reader/#per-query-parameters). The multivariate variant applies these business policies independently to each input channel {{% available_from "v1.30.2" anomaly %}}, so one model can represent combinations such as temperature above expected, power above expected, and clock below expected.
The multivariate variant uses `class: temporal_envelope_multivariate` or `model.online.TemporalEnvelopeMultivariateModel` and adds:
@@ -900,10 +896,13 @@ models:
# See https://docs.victoriametrics.com/anomaly-detection/components/models/#common-args
#
# provide_series: ['anomaly_score', 'yhat', 'yhat_lower', 'yhat_upper']
# schedulers: [all scheduler aliases defined in `scheduler` section]
# queries: [all query aliases defined in `reader.queries` section]
# detection_direction: 'both' # meaning both drops and spikes will be captured
# min_dev_from_expected: [0.0, 0.0] # meaning, no minimal threshold is applied to prevent smaller anomalies
# schedulers: [
# all scheduler aliases defined in `scheduler` section,
# ]
# queries: [
# all query aliases defined in `reader.queries` section,
# ]
# Configure detection_direction and minimum-deviation policies under reader.queries.<alias> (query-level from v1.30.2).
# scale: [1.0, 1.0] # if needed, prediction intervals' width can be increased (>1) or narrowed (<1)
# clip_predictions: False # if data_range for respective `queries` is set in reader, `yhat.*` columns will be clipped
# anomaly_score_outside_data_range: 1.01 # auto anomaly score (1.01) if `y` (real value) is outside of data_range, if set
@@ -967,10 +966,13 @@ models:
# See https://docs.victoriametrics.com/anomaly-detection/components/models/#common-args
#
# provide_series: ['anomaly_score', 'yhat', 'yhat_lower', 'yhat_upper']
# schedulers: [all scheduler aliases defined in `scheduler` section]
# queries: [all query aliases defined in `reader.queries` section]
# detection_direction: 'both' # meaning both drops and spikes will be captured
# min_dev_from_expected: [0.0, 0.0] # meaning, no minimal threshold is applied to prevent smaller anomalies
# schedulers: [
# all scheduler aliases defined in `scheduler` section,
# ]
# queries: [
# all query aliases defined in `reader.queries` section,
# ]
# Configure detection_direction and minimum-deviation policies under reader.queries.<alias> (query-level from v1.30.2).
# scale: [1.0, 1.0] # if needed, prediction intervals' width can be increased (>1) or narrowed (<1)
# clip_predictions: False # if data_range for respective `queries` is set in reader, `yhat.*` columns will be clipped
# anomaly_score_outside_data_range: 1.01 # auto anomaly score (1.01) if `y` (real value) is outside of data_range, if set
@@ -1014,10 +1016,13 @@ models:
# See https://docs.victoriametrics.com/anomaly-detection/components/models/#common-args
#
# provide_series: ['anomaly_score', 'yhat', 'yhat_lower', 'yhat_upper']
# schedulers: [all scheduler aliases defined in `scheduler` section]
# queries: [all query aliases defined in `reader.queries` section]
# detection_direction: 'both' # meaning both drops and spikes will be captured
# min_dev_from_expected: [0.0, 0.0] # meaning, no minimal threshold is applied to prevent smaller anomalies
# schedulers: [
# all scheduler aliases defined in `scheduler` section,
# ]
# queries: [
# all query aliases defined in `reader.queries` section,
# ]
# Configure detection_direction and minimum-deviation policies under reader.queries.<alias> (query-level from v1.30.2).
# scale: [1.0, 1.0] # if needed, prediction intervals' width can be increased (>1) or narrowed (<1)
# clip_predictions: False # if data_range for respective `queries` is set in reader, `yhat.*` columns will be clipped
# anomaly_score_outside_data_range: 1.01 # auto anomaly score (1.01) if `y` (real value) is outside of data_range, if set
@@ -1060,10 +1065,13 @@ models:
# See https://docs.victoriametrics.com/anomaly-detection/components/models/#common-args
#
# provide_series: ['anomaly_score', 'yhat', 'yhat_lower', 'yhat_upper']
# schedulers: [all scheduler aliases defined in `scheduler` section]
# queries: [all query aliases defined in `reader.queries` section]
# detection_direction: 'both' # meaning both drops and spikes will be captured
# min_dev_from_expected: [0.0, 0.0] # meaning, no minimal threshold is applied to prevent smaller anomalies
# schedulers: [
# all scheduler aliases defined in `scheduler` section,
# ]
# queries: [
# all query aliases defined in `reader.queries` section,
# ]
# Configure detection_direction and minimum-deviation policies under reader.queries.<alias> (query-level from v1.30.2).
# scale: [1.0, 1.0] # if needed, prediction intervals' width can be increased (>1) or narrowed (<1)
# clip_predictions: False # if data_range for respective `queries` is set in reader, `yhat.*` columns will be clipped
# anomaly_score_outside_data_range: 1.01 # auto anomaly score (1.01) if `y` (real value) is outside of data_range, if set
@@ -1099,6 +1107,7 @@ Resulting metrics of the model are described [here](#vmanomaly-output).
- `tz_use_cyclical_encoding`{{% available_from "v1.18.0" anomaly %}} (bool): If set to `True`, applies [cyclical encoding technique](https://www.kaggle.com/code/avanwyk/encoding-cyclical-features-for-deep-learning) to timezone-aware seasonalities. Should be used with `tz_aware=True` and `tz_seasonalities`.
- `forecast_at`{{% available_from "v1.25.3" anomaly %}} (list[str]): Specifies future relative offsets for which forecasts should be generated (e.g., `['1h', '1d']`). Works similarly to [predict_linear](https://docs.victoriametrics.com/victoriametrics/metricsql/#predict_linear) in MetricQL, but with more flexibility and seasonality support - produced series will have *the same timestamp* as the other [output](#vmanomaly-output) series, but with the forecasted value for the *future timestamp*. Defaults to `[]` (empty list, meaning no future forecasts are produced). If set, `provide_series` must include at least `yhat` for point-wise forecasts (and `yhat_lower` or/and `yhat_upper` for respective confidence intervals). For example, if `forecast_at` is set to `['1h', '1d']`, the model will produce forecasts for both the next hour and the next day, and these series can be accessed by `yhat_1h`, `yhat_lower_1h`, `yhat_upper_1h`, `yhat_1d`, `yhat_lower_1d`, and `yhat_upper_1d` in the output, respectively. See [FAQ](https://docs.victoriametrics.com/anomaly-detection/faq/#forecasting) for more details.
> [!WARNING]
> `forecast_at` parameter can lead to **significant increase in active timeseries** if you have a lot of time series returned by your queries, as it will produce additional series for each of the future timestamps specified in `forecast_at` (optionally multiplied by 1-3 if interval forecasts are included). For example, if you have 1000 time series returned by your query and set `forecast_at` to `[1h, 1d, 1w]`, and `provide_series` includes `yhat_lower` and `yhat_upper`, it will produce 1000 (series) * 3 (intervals) * 3 (predictions, point + interval) = 9000 additional timeseries. Consider using it only on small subset of metrics (e.g. grouped by `host` or `region`) to avoid this issue, as it also **proportionally (to the number of `forecast_at` elements) increases the timings of inference calls**.
- `compression` {{% available_from "v1.28.1" anomaly %}} (dict, optional): Configuration for downsampling input data before fitting the model. Useful for high-frequency data to reduce CPU and RAM/disk load and improve model performance. The `compression` block supports the following parameters:
@@ -1121,10 +1130,13 @@ models:
# See https://docs.victoriametrics.com/anomaly-detection/components/models/#common-args
#
# provide_series: ['anomaly_score', 'yhat', 'yhat_lower', 'yhat_upper', 'trend']
# schedulers: [all scheduler aliases defined in `scheduler` section]
# queries: [all query aliases defined in `reader.queries` section]
# detection_direction: 'both' # meaning both drops and spikes will be captured
# min_dev_from_expected: [0.0, 0.0] # meaning, no minimal threshold is applied to prevent smaller anomalies
# schedulers: [
# all scheduler aliases defined in `scheduler` section,
# ]
# queries: [
# all query aliases defined in `reader.queries` section,
# ]
# Configure detection_direction and minimum-deviation policies under reader.queries.<alias> (query-level from v1.30.2).
# scale: [1.0, 1.0] # if needed, prediction intervals' width can be increased (>1) or narrowed (<1)
# clip_predictions: False # if data_range for respective `queries` is set in reader, `yhat.*` columns will be clipped
# anomaly_score_outside_data_range: 1.01 # auto anomaly score (1.01) if `y` (real value) is outside of data_range, if set
@@ -1155,10 +1167,13 @@ models:
# See https://docs.victoriametrics.com/anomaly-detection/components/models/#common-args
#
# provide_series: ['anomaly_score', 'yhat', 'yhat_lower', 'yhat_upper', 'trend']
# schedulers: [all scheduler aliases defined in `scheduler` section]
# queries: [all query aliases defined in `reader.queries` section]
# detection_direction: 'both' # meaning both drops and spikes will be captured
# min_dev_from_expected: [0.0, 0.0] # meaning, no minimal threshold is applied to prevent smaller anomalies
# schedulers: [
# all scheduler aliases defined in `scheduler` section,
# ]
# queries: [
# all query aliases defined in `reader.queries` section,
# ]
# Configure detection_direction and minimum-deviation policies under reader.queries.<alias> (query-level from v1.30.2).
# scale: [1.0, 1.0] # if needed, prediction intervals' width can be increased (>1) or narrowed (<1)
# clip_predictions: False # if data_range for respective `queries` is set in reader, `yhat.*` columns will be clipped
# anomaly_score_outside_data_range: 1.01 # auto anomaly score (1.01) if `y` (real value) is outside of data_range, if set
@@ -1254,8 +1269,12 @@ models:
# See https://docs.victoriametrics.com/anomaly-detection/components/models/#common-args
#
# provide_series: ['anomaly_score', 'yhat', 'yhat_lower', 'yhat_upper']
# schedulers: [all scheduler aliases defined in `scheduler` section]
# queries: [all query aliases defined in `reader.queries` section]
# schedulers: [
# all scheduler aliases defined in `scheduler` section,
# ]
# queries: [
# all query aliases defined in `reader.queries` section,
# ]
# anomaly_score_outside_data_range: 1.01 # auto anomaly score (1.01) if `y` (real value) is outside of data_range, if set
```
@@ -1316,10 +1335,13 @@ models:
# See https://docs.victoriametrics.com/anomaly-detection/components/models/#common-args
#
# provide_series: ['anomaly_score', 'yhat', 'yhat_lower', 'yhat_upper']
# schedulers: [all scheduler aliases defined in `scheduler` section]
# queries: [all query aliases defined in `reader.queries` section]
# detection_direction: 'both' # meaning both drops and spikes will be captured
# min_dev_from_expected: [0.0, 0.0] # meaning, no minimal threshold is applied to prevent smaller anomalies
# schedulers: [
# all scheduler aliases defined in `scheduler` section,
# ]
# queries: [
# all query aliases defined in `reader.queries` section,
# ]
# Configure detection_direction and minimum-deviation policies under reader.queries.<alias> (query-level from v1.30.2).
# scale: [1.0, 1.0] # if needed, prediction intervals' width can be increased (>1) or narrowed (<1)
# clip_predictions: False # if data_range for respective `queries` is set in reader, `yhat.*` columns will be clipped
# anomaly_score_outside_data_range: 1.01 # auto anomaly score (1.01) if `y` (real value) is outside of data_range, if set
@@ -1359,10 +1381,13 @@ models:
# See https://docs.victoriametrics.com/anomaly-detection/components/models/#common-args
#
# provide_series: ['anomaly_score', 'yhat', 'yhat_lower', 'yhat_upper']
# schedulers: [all scheduler aliases defined in `scheduler` section]
# queries: [all query aliases defined in `reader.queries` section]
# detection_direction: 'both' # meaning both drops and spikes will be captured
# min_dev_from_expected: [0.0, 0.0] # meaning, no minimal threshold is applied to prevent smaller anomalies
# schedulers: [
# all scheduler aliases defined in `scheduler` section,
# ]
# queries: [
# all query aliases defined in `reader.queries` section,
# ]
# Configure detection_direction and minimum-deviation policies under reader.queries.<alias> (query-level from v1.30.2).
# scale: [1.0, 1.0] # if needed, prediction intervals' width can be increased (>1) or narrowed (<1)
# clip_predictions: False # if data_range for respective `queries` is set in reader, `yhat.*` columns will be clipped
# anomaly_score_outside_data_range: 1.01 # auto anomaly score (1.01) if `y` (real value) is outside of data_range, if set
@@ -1433,7 +1458,7 @@ Create `custom_model.py` with a `CustomModel` class derived from `Model`. A conc
- `serialize`, which returns `bytes` suitable for on-disk storage;
- `deserialize`, which restores the same model from bytes or a file path.
Model-specific configuration is passed through the `args` mapping. The example below learns a stationary normal interval. It emits the standard forecast columns and uses the base-class anomaly-score calculation, so common settings such as `detection_direction`, `data_range`, `scale`, and minimum deviations continue to work.
Model-specific configuration is passed through the `args` mapping. The example below learns a stationary normal interval. It emits the standard forecast columns and uses the base-class anomaly-score calculation, so query policies such as `detection_direction`, `data_range`, and minimum deviations, together with model settings such as `scale`, continue to work.
```python
from pickle import dumps
@@ -1561,7 +1586,7 @@ See the [component configuration reference](https://docs.victoriametrics.com/ano
Pull the `vmanomaly` image:
```sh
docker pull victoriametrics/vmanomaly:v1.30.1
docker pull victoriametrics/vmanomaly:v1.30.2
```
Mount the module at `/vmanomaly/src/model/custom.py`, which matches the configured import path `model.custom.CustomModel`. Validate the complete configuration with `--dryRun` before starting the long-running service.
@@ -1571,7 +1596,7 @@ docker run --rm \
-v "$PWD/license:/license:ro" \
-v "$PWD/custom_model.py:/vmanomaly/src/model/custom.py:ro" \
-v "$PWD/config.yaml:/config.yaml:ro" \
victoriametrics/vmanomaly:v1.30.1 \
victoriametrics/vmanomaly:v1.30.2 \
/config.yaml \
--licenseFile=/license \
--dryRun
@@ -1667,10 +1692,13 @@ models:
# See https://docs.victoriametrics.com/anomaly-detection/components/models/#common-args
#
# provide_series: ['anomaly_score', 'yhat', 'yhat_lower', 'yhat_upper']
# schedulers: [all scheduler aliases defined in `scheduler` section]
# queries: [all query aliases defined in `reader.queries` section]
# detection_direction: 'both' # meaning both drops and spikes will be captured
# min_dev_from_expected: [0.0, 0.0] # meaning, no minimal threshold is applied to prevent smaller anomalies
# schedulers: [
# all scheduler aliases defined in `scheduler` section,
# ]
# queries: [
# all query aliases defined in `reader.queries` section,
# ]
# Configure detection_direction and minimum-deviation policies under reader.queries.<alias> (query-level from v1.30.2).
# scale: [1.0, 1.0] # if needed, prediction intervals' width can be increased (>1) or narrowed (<1)
# clip_predictions: False # if data_range for respective `queries` is set in reader, `yhat.*` columns will be clipped
# anomaly_score_outside_data_range: 1.01 # auto anomaly score (1.01) if `y` (real value) is outside of data_range, if set
@@ -1709,10 +1737,13 @@ models:
# See https://docs.victoriametrics.com/anomaly-detection/components/models/#common-args
#
# provide_series: ['anomaly_score', 'yhat', 'yhat_lower', 'yhat_upper']
# schedulers: [all scheduler aliases defined in `scheduler` section]
# queries: [all query aliases defined in `reader.queries` section]
# detection_direction: 'both' # meaning both drops and spikes will be captured
# min_dev_from_expected: [0.0, 0.0] # meaning, no minimal threshold is applied to prevent smaller anomalies
# schedulers: [
# all scheduler aliases defined in `scheduler` section,
# ]
# queries: [
# all query aliases defined in `reader.queries` section,
# ]
# Configure detection_direction and minimum-deviation policies under reader.queries.<alias> (query-level from v1.30.2).
# scale: [1.0, 1.0] # if needed, prediction intervals' width can be increased (>1) or narrowed (<1)
# clip_predictions: False # if data_range for respective `queries` is set in reader, `yhat.*` columns will be clipped
# anomaly_score_outside_data_range: 1.01 # auto anomaly score (1.01) if `y` (real value) is outside of data_range, if set

View File

@@ -1,5 +1,6 @@
---
title: Monitoring
description: "Self-monitoring via push and pull models."
weight: 5
menu:
docs:
@@ -333,6 +334,14 @@ For detailed guidance on configuring mTLS parameters such as `verify_tls`, `tls_
<tr>
<td>
<span style="white-space: nowrap;">`vmanomaly_native_threads_per_worker`</span>
</td>
<td>Gauge</td>
<td>Effective maximum native numerical-library threads per model worker{{% available_from "v1.30.2" anomaly %}} after resolving [`settings.native_threads_per_worker`](https://docs.victoriametrics.com/anomaly-detection/components/settings/#parallelization) against the effective worker count and container-aware CPU capacity.</td>
</tr>
<tr>
<td>
<span style="white-space: nowrap;">`vmanomaly_config_entities`</span>
</td>
<td>Gauge</td>

View File

@@ -1,5 +1,6 @@
---
title: Reader
description: "Data reader configuration. MetricsQL queries from VictoriaMetrics or LogsQL from VictoriaLogs/VictoriaTraces."
weight: 2
menu:
docs:
@@ -59,7 +60,7 @@ reader:
step: '10s' # individual step for this query, will be filled with `sampling_period` from the root level
data_range: ['-inf', 'inf'] # by default, no constraints applied on data range
tz: 'UTC' # by default, tz-free data is used throughout the model lifecycle
# new query-level arguments will be added in backward-compatible way in future releases
# from v1.30.2, explicitly add detection_direction and minimum-deviation policies here when needed
```
{{% /collapse %}}
@@ -85,6 +86,16 @@ There is change {{% available_from "v1.13.0" anomaly %}} of [`queries`](https://
> If not set explicitly (or if older config style prior to [v1.13.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1130)) is used, then it is set to reader-level `data_range` arg{{% available_from "v1.18.1" anomaly %}}
> Configuring `data_range` in a model is {{% deprecated_from "v1.30.2" anomaly %}}. Configure it under `reader.queries.<alias>` so the KPI domain remains the same when the query is attached to different models. Existing model-level values remain compatible as model-local fallbacks when the query does not define an explicit value.
- `detection_direction`{{% available_from "v1.30.2" anomaly %}} (`both`, `above_expected`, or `below_expected`): controls whether deviations on both sides, only above the expected value, or only below it can produce anomaly scores. The default is `both`. See [detection direction](https://docs.victoriametrics.com/anomaly-detection/components/models/#detection-direction) for behavior details.
- `min_dev_from_expected`{{% available_from "v1.30.2" anomaly %}} (float or one/two-element list[float]): ignores deviations smaller than the configured absolute threshold. A scalar or one-element list applies to both directions; a two-element list configures lower and upper deviations separately. See [minimal deviation from expected](https://docs.victoriametrics.com/anomaly-detection/components/models/#minimal-deviation-from-expected).
- `min_rel_dev_from_expected`{{% available_from "v1.30.2" anomaly %}} (float or one/two-element list[float]): ignores deviations smaller than the configured percentage of the absolute expected value. A scalar or one-element list applies to both directions; a two-element list configures lower and upper percentages separately. See [minimal relative deviation from expected](https://docs.victoriametrics.com/anomaly-detection/components/models/#minimal-relative-deviation-from-expected).
> Configuring `detection_direction`, `min_dev_from_expected`, or `min_rel_dev_from_expected` in a model is {{% deprecated_from "v1.30.2" anomaly %}}. Query-level values are authoritative. Existing model-level values remain compatible only as model-local fallbacks for attached queries that do not define the corresponding policy.
- `max_points_per_query`{{% available_from "v1.17.0" anomaly %}} (int): Optional arg, overrides how `search.maxPointsPerTimeseries` flag{{% available_from "v1.14.1" anomaly %}} impacts `vmanomaly` on splitting long `fit_window` [queries](https://docs.victoriametrics.com/anomaly-detection/components/reader/#vm-reader) into smaller sub-intervals. This helps users avoid hitting the `search.maxQueryDuration` limit for individual queries by distributing initial query across multiple subquery requests with minimal overhead. Set less than `search.maxPointsPerTimeseries` if hitting `maxQueryDuration` limits. If set on a query-level, it overrides the global `max_points_per_query` (reader-level).
- `tz`{{% available_from "v1.18.0" anomaly %}} (string): this optional argument enables timezone specification per query, overriding the readers default `tz`. This setting helps to account for local timezone shifts, such as [DST](https://en.wikipedia.org/wiki/Daylight_saving_time), in models that are sensitive to seasonal variations (e.g., [`TemporalEnvelopeModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#temporal-envelope) or [`OnlineQuantileModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-seasonal-quantile)).
@@ -115,7 +126,10 @@ reader:
ingestion_rate_t1:
expr: 'sum(rate(vm_rows_inserted_total[5m])) by (type) > 0'
step: '2m' # overrides global `sampling_period` of 1m
data_range: [10, 'inf'] # meaning only positive values > 10 are expected, i.e. a value `y` < 10 will trigger anomaly score > 1
data_range: [10, 'inf'] # query-level business policy from v1.30.2; y < 10 triggers anomaly score > 1
detection_direction: 'above_expected' # query-level from v1.30.2; only spikes can be anomalous
min_dev_from_expected: [0, 5] # query-level from v1.30.2; ignore upward deviations smaller than 5
min_rel_dev_from_expected: [0, 15] # query-level from v1.30.2; ignore upward deviations below 15%
max_points_per_query: 5000 # overrides reader-level value of 10000 for `ingestion_rate` query
tz: 'America/New_York' # to override reader-wise `tz`
tenant_id: '1:0' # overriding tenant_id to isolate data
@@ -302,6 +316,19 @@ Optional timeout {{% available_from "v1.30.0" anomaly %}} for post-fetch process
<tr>
<td>
<span style="white-space: nowrap;">`workers`</span>
</td>
<td>
`0`
</td>
<td>
Maximum concurrent datasource fetch threads {{% available_from "v1.30.2" anomaly %}}. `0` selects a bounded value automatically from the number of queries and available CPUs. A positive value sets an explicit cap for queries and disk-streamed split-query chunks.
</td>
</tr>
<tr>
<td>
<span style="white-space: nowrap;">`verify_tls`</span>
</td>
<td>
@@ -510,6 +537,7 @@ reader:
timeout: '30s' # backward-compatible default for both phases
fetch_timeout: '30s' # timeout for each datasource request, overrides `timeout` if set
processing_timeout: '1m' # timeout for preparing fetched series for fit/infer, overrides `timeout` if set
workers: 0 # automatic bounded datasource concurrency; set a positive value for an explicit cap
query_from_last_seen_timestamp: True # false by default
latency_offset: '1ms'
series_processing_batch_size: 8
@@ -896,6 +924,19 @@ Optional timeout {{% available_from "v1.30.0" anomaly %}} for post-fetch process
<tr>
<td>
<span style="white-space: nowrap;">`workers`</span>
</td>
<td>
`0`
</td>
<td>
Maximum concurrent datasource fetch threads {{% available_from "v1.30.2" anomaly %}}. `0` selects a bounded value automatically from the number of queries and available CPUs. A positive value sets an explicit cap for queries and disk-streamed split-query chunks.
</td>
</tr>
<tr>
<td>
<span style="white-space: nowrap;">`verify_tls`</span>
</td>
<td>
@@ -1037,12 +1078,15 @@ reader:
timeout: '30s' # backward-compatible default for both phases
fetch_timeout: '30s' # timeout for each datasource request, overrides `timeout` if set
processing_timeout: '1m' # timeout for preparing fetched series for fit/infer, overrides `timeout` if set
workers: 0 # automatic bounded datasource concurrency; set a positive value for an explicit cap
queries:
# one query returning 1 result fields (avg_duration), it will have __name__ label (series name) as `duration_30m__avg`
duration_avg_30m:
expr: "* | stats avg(duration) as avg" # initial LogsQL expression
step: '2m' # overrides global `sampling_period` of 1m
data_range: [0, 'inf'] # meaning only positive values > 0 are expected, i.e. a value `y` < 0 will trigger anomaly score > 1
data_range: [0, 'inf'] # query-level business policy from v1.30.2; y < 0 triggers anomaly score > 1
detection_direction: 'above_expected' # query-level from v1.30.2
min_rel_dev_from_expected: [0, 20] # query-level from v1.30.2; ignore upward deviations below 20%
tz: 'America/New_York' # to override reader-wise `tz`
# tenant_id: '1:0' # overriding tenant_id to isolate data
# offset: '-15s' # to override reader-wise `offset` and query data 15 seconds earlier to account for data collection delays

View File

@@ -1,5 +1,6 @@
---
title: Scheduler
description: "Scheduling configuration. Inference frequency and training time range."
weight: 3
menu:
docs:
@@ -70,6 +71,7 @@ options={`"scheduler.periodic.PeriodicScheduler"`, `"scheduler.oneoff.OneoffSche
## Periodic scheduler
> [!WARNING]
> If `start_from` [parameter](#parameters-1) is used, it's suggested to also set `restore_state: true` in the [Settings section](https://docs.victoriametrics.com/anomaly-detection/components/settings/#state-restoration) of a config, so that the scheduler can restore its state from the previous run **if terminated or restarted in between scheduled runs** and continue producing anomaly scores without interruptions, otherwise the service will be idle until future `start_from` time is reached. E.g. if `start_from` is set to `20:00` and the service is started and then terminated and restarted at `20:30`, it will not produce any anomaly scores until the next day's `20:00` is reached (+23:30 of being idle), which introduces inconvenience for the users.
> {{% available_from "v1.30.0" anomaly %}} If a periodic scheduler worker exits unexpectedly, the service attempts bounded restarts with exponential backoff instead of shutting down unrelated schedulers. Monitor [`vmanomaly_scheduler_alive`](https://docs.victoriametrics.com/anomaly-detection/components/monitoring/#startup-metrics) and `vmanomaly_scheduler_restarts_total` to alert on persistent failures.
@@ -196,6 +198,7 @@ This configuration specifies that `vmanomaly` will calculate a 14-day time windo
## Oneoff scheduler
> [!WARNING]
> As of latest version, the Oneoff scheduler can't be explicitly used with a combination of [stateful service](https://docs.victoriametrics.com/anomaly-detection/components/settings/#state-restoration). It is designed to run once and exit, so it does not maintain state across runs. A warning will be raised in logs and internal state for such scheduler will not be saved and restored upon restart. If you need to run the scheduler periodically and/or maintain state, consider using the [Periodic scheduler](#periodic-scheduler) instead.
### Parameters
@@ -367,6 +370,7 @@ schedulers:
> {{% available_from "v1.26.0" anomaly %}} `BacktestingScheduler` in [inference-only](https://docs.victoriametrics.com/anomaly-detection/components/scheduler/#inference-only-mode) mode is used in UI for backtesting configurations on historical data to verify that it works as expected before it goes live. See [vmanomaly UI](https://docs.victoriametrics.com/anomaly-detection/ui/) on how to access and use the UI.
> [!WARNING]
> As of latest version, the Backtesting scheduler can't be explicitly used with a combination of [state restoration](https://docs.victoriametrics.com/anomaly-detection/components/settings/#state-restoration). It is designed to run once and exit, so it does not maintain state across runs. A warning will be raised in logs and internal state for such scheduler will not be saved and restored upon restart. If you need to run the scheduler periodically and/or maintain state, consider using the [Periodic scheduler](#periodic-scheduler) instead.
> A new, more intuitive backtesting mode is available {{% available_from "v1.22.1" anomaly %}}. In **Inference only** mode, the window you specify via `[from, to]` (or `[from_iso, to_iso]`) is used *solely for inference*, and the corresponding training (“fit”) windows are determined automatically. To enable this behavior, set:

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -1,5 +1,6 @@
---
title: Server
description: "HTTP server. REST API, /metrics endpoint, and web UI."
weight: 7
menu:
docs:

View File

@@ -1,5 +1,6 @@
---
title: Settings
description: "Global settings for the anomaly detection service."
weight: 6
menu:
docs:
@@ -16,7 +17,7 @@ aliases:
Through the **Settings** section of a config, you can configure the following parameters of the anomaly detection service:
- [Anomaly score outside data range](#anomaly-score-outside-data-range) - specific anomaly score fo values outside the expected data range of particular query
- [Parallelization](#parallelization) - number of workers to run workloads in parallel
- [Parallelization](#parallelization) - process workers and native numerical-library threads used by each worker
- [State restoration](#state-restoration) - whether to restore models' state in between runs if the service is restarted or stopped
## Anomaly Score Outside Data Range
@@ -36,7 +37,7 @@ settings:
schedulers:
periodic:
class: periodic
fit_every: 5m
fit_every: 1000d # bootstrap-only schedule; use a finite cadence if accumulated state must be reset
fit_window: 3h
infer_every: 30s
# other schedulers
@@ -45,12 +46,14 @@ models:
zscore_online_inherited:
class: zscore_online
z_threshold: 3.5
decay: 0.99 # give more weight to recent data while using the bootstrap-only fit schedule
clip_predictions: True
# will be inherited from settings.anomaly_score_outside_data_range
# anomaly_score_outside_data_range: 5.0
zscore_online_override:
class: zscore_online
z_threshold: 3.5
decay: 0.99 # give more weight to recent data while using the bootstrap-only fit schedule
clip_predictions: True
anomaly_score_outside_data_range: 1.5 # will override settings.anomaly_score_outside_data_range
# other models
@@ -86,24 +89,29 @@ monitoring:
# other monitoring settings
```
The examples on this page use `fit_every: 1000d` as an effectively bootstrap-only schedule. This is appropriate when an online model has a suitable forgetting or reactivity mechanism, such as `zscore_online` with `decay < 1`. If outdated history must be discarded explicitly, choose a finite fit cadence instead; each fit resets the online model state from the configured `fit_window`.
## Parallelization
The `n_workers` argument allows you to explicitly specify the number of workers for internal parallelization of the service. This can help improve performance on multicore systems by allowing the service to process multiple tasks in parallel. For backward compatibility, it's set to `1` by default, meaning that the service will run in a single-threaded mode. It should be an integer greater than or equal to `-1`, where `-1` and `0` means that the service will automatically inherit the number of workers based on the number of available CPU cores.
The `n_workers` argument allows you to explicitly specify the number of process workers for internal parallelization of the service. This can help improve performance on multicore systems by allowing the service to process multiple tasks in parallel. For backward compatibility, it is set to `1` by default. It should be an integer greater than or equal to `-1`; values `-1` and `0` use the number of CPU cores available to the service, including container CPU limits.
Increasing the number can be particularly useful when dealing with a high volume of queries returning many (long) timeseries.
Decreasing the number can be useful when running the service on a system with limited resources or when you want to reduce the load on the system.
The `native_threads_per_worker` argument {{% available_from "v1.30.2" anomaly %}} limits [native numerical-library threads](https://scikit-learn.org/stable/computing/parallelism.html#oversubscription-spawning-too-many-threads), such as OpenBLAS threads, inside each model worker. Its default `0` divides the CPU capacity available to the service across effective workers automatically. A positive integer requests an explicit per-worker limit, capped by the CPU share available to that worker. This avoids oversubscription and CPU throttling when every process would otherwise start its own multi-threaded numerical workload. Both `n_workers` and `native_threads_per_worker` are startup settings and require a service restart to change.
- **Increasing** the number can be particularly useful when dealing with a high volume of queries returning many (long) timeseries.
- **Decreasing** the number can be useful when running the service on a system with limited resources or when you want to reduce the load on the system.
Here's an example configuration that uses 4 workers for service's internal parallelization:
```yaml
settings:
n_workers: 4
native_threads_per_worker: 0 # automatically divide available CPU capacity across workers
restore_state: False # do not restore state from previous run
schedulers:
periodic:
class: periodic
fit_every: 5m
fit_every: 1000d # bootstrap-only schedule; use a finite cadence if accumulated state must be reset
fit_window: 3h
infer_every: 30s
# other schedulers
@@ -112,6 +120,7 @@ models:
zscore_online_override:
class: zscore_online
z_threshold: 3.5
decay: 0.99 # give more weight to recent data while using the bootstrap-only fit schedule
clip_predictions: True
# other models
@@ -149,10 +158,11 @@ monitoring:
> This feature is best used with config [hot-reloading](https://docs.victoriametrics.com/anomaly-detection/components/#hot-reload) {{% available_from "v1.25.0" anomaly %}} for increased deployment flexibility.
The `restore_state` argument {{% available_from "v1.24.0" anomaly %}} makes `vmanomaly` service **stateful** by persisting and restoring state between runs. If enabled, the service will save the state of anomaly detection models and their training data to local filesystem, allowing for seamless continuation of operations after service restarts.
The `restore_state` argument {{% available_from "v1.24.0" anomaly %}} makes `vmanomaly` service **stateful** by persisting and restoring service metadata and fitted model state between runs, allowing seamless continuation after service restarts.
By default, `restore_state` is set to `false`, meaning the service will start fresh on each restart, to maintain backward compatibility.
> [!WARNING]
> This feature requires enabling [on-disk mode](https://docs.victoriametrics.com/anomaly-detection/faq/#on-disk-mode) for the models and data. If not enabled, the service will exit with an error when `restore_state` is set to `true`.
### Benefits
@@ -164,15 +174,17 @@ This feature improves the experience of using the anomaly detection service in s
### How it works
**Storage**: The service dumps its state into a database file located at `$VMANOMALY_MODEL_DUMPS_DIR/vmanomaly.db`. This database contains metadata about model configurations, schedulers and references to the trained model instances and their respective data.
**Storage**: The service dumps its state into a database file located at `$VMANOMALY_MODEL_DUMPS_DIR/vmanomaly.db`. This database contains metadata about model configurations and schedulers, together with references to trained model artifacts. Scheduler-managed Parquet data is temporary fit input rather than durable model state.
**State restoration**: When the service starts with `restore_state` set to `true`, it will:
1. Check for the existence of the database file in the specified directory.
2. If the file does not exist, it will create a new database file and initialize the state with the current configuration, training models as needed. If the file exists, then it compares the loaded state with the current configuration to ensure compatibility - what can be reused and what needs to be retrained (e.g., if the model class or hyperparameters have changed, it will not restore the state for that model, same for schedulers or reader queries). For reusable components, previously saved state, including model configurations, trained model instances, and their training data, will be restored.
3. Subsequently, it will check for model "staleness" and retrain models if necessary, based on the current configuration and the last training time stored in the database vs next scheduled training time. If the model is **actual**, it will continue to use the previously trained model instances or its training data. If the model is **stale** (e.g. `fit_every` time has passed since the last training), it will retrain the model using the latest data of `fit_window` length from VictoriaMetrics TSDB.
2. If the file does not exist, it will create a new database file and initialize the state with the current configuration, training models as needed. If the file exists, then it compares the loaded state with the current configuration to determine what can be reused and what needs to be retrained (for example, a changed model class, hyperparameter, scheduler, or reader query invalidates the affected state). Compatible model configurations and trained model instances are restored.
3. Subsequently, it checks model "staleness" and retrains models if necessary, based on the current configuration and the last training time stored in the database versus the next scheduled training time. If the model is **actual**, it continues to use the previously trained model instance. If the model is **stale** (for example, `fit_every` has passed since the last training), it reads the latest `fit_window` from VictoriaMetrics and retrains the model.
**State update**: The service periodically saves the updated state after each "atomic" operations, such as (model_alias, query_alias)-based training or inference. This ensures that the state is always up-to-date and can be restored in case of a service restart. [Online models](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-models) are also updated after each inference, while [offline models](https://docs.victoriametrics.com/anomaly-detection/components/models/#offline-models) are only saved after each training operation as they do not change the state during consecutive fit calls.
**Fit-data cleanup**: {{% available_from "v1.30.2" anomaly %}} Each scheduler-managed Parquet generation is removed after all dependent univariate or multivariate models finish fitting and commit their state. Failed or overlapping fits retain their own generation until it is safe to clean up. This keeps the initial bootstrap window available while it is in use without retaining it for the full `fit_every` interval.
**Cleanup behavior**: When `restore_state` is switched from `true` to `false`, the database file is automatically removed on the next service startup to prevent inconsistent behavior. All the artifacts (such as model dumps and data dumps) will be removed as well, so the service will start fresh without any previous state.
Here's an example configuration that enables state restoration:
@@ -185,7 +197,7 @@ settings:
schedulers:
periodic:
class: periodic
fit_every: 5m
fit_every: 1000d # bootstrap-only schedule; use a finite cadence if accumulated state must be reset
fit_window: 3h
infer_every: 30s
# other schedulers
@@ -194,6 +206,7 @@ models:
zscore_online:
class: zscore_online
z_threshold: 3.5
decay: 0.99 # give more weight to recent data while using the bootstrap-only fit schedule
clip_predictions: True
# other models
@@ -242,16 +255,19 @@ settings:
schedulers:
periodic_1d:
class: periodic
fit_every: 1h
fit_every: 1000d # bootstrap-only schedule; use a finite cadence if accumulated state must be reset
infer_every: 30s
fit_window: 24h
models:
zscore_online:
class: zscore_online
z_threshold: 3.5
decay: 0.99 # give more weight to recent data while using the bootstrap-only fit schedule
schedulers: ['periodic_1d']
temporal_envelope:
class: temporal_envelope
alpha: 0.005 # adapt the trend while using the bootstrap-only fit schedule
loss_reactivity: 5 # allow new deviations to update the envelope
schedulers: ['periodic_1d']
queries: ['q1', 'q2']
seasonalities: ['hod_smooth', 'dow_smooth']
@@ -268,7 +284,7 @@ reader:
# other components like writer, monitoring, etc.
```
if the service is restarted in less than 1 hour after the last training (now < next scheduled fit time), it will restore the state of the `zscore_online` and `temporal_envelope` models if their signature (class, hyperparameters, schedulers, etc.) has not changed. It will load the trained model instances or their training data from disk and continue producing [anomaly scores](https://docs.victoriametrics.com/anomaly-detection/faq/#what-is-anomaly-score) without retraining. If there are changes or new queries added to the configuration, the service will add these to scheduled jobs for fit and infer. That's what is changed and what is restored in a config below:
if the service is restarted before the next scheduled fit, it will restore the state of the `zscore_online` and `temporal_envelope` models if their signature (class, hyperparameters, schedulers, etc.) has not changed. It loads trained model instances from disk and continues producing [anomaly scores](https://docs.victoriametrics.com/anomaly-detection/faq/#what-is-anomaly-score) without retraining. If there are changes or new queries added to the configuration, the service will add these to scheduled jobs for fit and infer. That's what is changed and what is restored in a config below:
```yaml
settings:
@@ -277,16 +293,19 @@ settings:
schedulers:
periodic_1d: # can be fully reused, no changes
class: periodic
fit_every: 1h # unchanged, still fits every hour
fit_every: 1000d # unchanged bootstrap-only schedule
infer_every: 30s # unchanged, still infers every 30 seconds
fit_window: 24h # unchanged, still fits on the last 24 hours of data
models:
zscore_online: # can't be reused, because its `z_threshold` has changed
class: zscore_online # unchanged, still the same model class
z_threshold: 3.0 # changed, needs retraining!
decay: 0.99 # unchanged forgetting factor
schedulers: ['periodic_1d'] # unchanged, still attached to the same scheduler
temporal_envelope: # can be partially reused, because its class and schedulers are unchanged but queries have changed
class: temporal_envelope # unchanged, still the same model class
alpha: 0.005 # unchanged trend reactivity
loss_reactivity: 5 # unchanged envelope reactivity
schedulers: ['periodic_1d'] # unchanged, still attached to the same scheduler
queries: ['q1', 'q3'] # changed, added new query 'q3', drops 'q2', so (temporal_envelope, q2) should be trained from scratch
seasonalities: ['hod_smooth', 'dow_smooth'] # unchanged
@@ -314,31 +333,31 @@ This means that the service upon restart:
## Retention
{{% available_from "v1.28.1" anomaly %}} The `retention` argument sets a [time to live](https://en.wikipedia.org/wiki/Time_to_live) (TTL) for service artifacts such as stored model instances and training data. At each `check_interval`, the service removes artifacts that have not been used for inference or refitting within `ttl`. This bounds stale resource usage in long-running deployments.
{{% available_from "v1.28.1" anomaly %}} The `retention` argument sets a [time to live](https://en.wikipedia.org/wiki/Time_to_live) (TTL) for stored model instances. At each `check_interval`, the service removes instances that have not been used for inference or refitting within `ttl`. This bounds stale resource usage in long-running deployments. Temporary scheduler-managed fit data follows the [fit-data cleanup lifecycle](#how-it-works) independently.
### Use Cases
- With **[online models](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-models)** as they continuously create model instances for new timeseries over time during inference calls, especially when combined with [periodic schedulers](https://docs.victoriametrics.com/anomaly-detection/components/scheduler/#periodic-scheduler) with infrequent `fit_every` (say, `90d`).
- In deployments where **the set of monitored timeseries changes frequently**, leading to accumulation of unused model instances and training data over time, due to high churn rate or relabeling of metrics.
- When using **[state restoration](https://docs.victoriametrics.com/anomaly-detection/components/settings/#state-restoration) feature** which improves fault tolerance, but may retain all model instances and their training data for considerable time, potentially leading to high disk or RAM usage.
- In deployments where **the set of monitored timeseries changes frequently**, leading to accumulation of unused model instances due to high churn rate or relabeling of metrics.
- When using **[state restoration](https://docs.victoriametrics.com/anomaly-detection/components/settings/#state-restoration)**, which improves fault tolerance but can retain inactive model instances unless retention is configured.
### Configuration
The section is **backward-compatible and disabled by default**, meaning that all model instances and their training data are retained unless:
The section is **backward-compatible and disabled by default**, meaning that model instances are retained unless:
- The service is restarted with `restore_state` set to `false`, which triggers a cleanup of all stored artifacts.
- The models are marked as outdated once scheduled re-fitting is due, leading to retraining and replacement of previous artifacts.
`ttl` argument defines the time-to-live period for model instances and their training data. It should be a valid period string (e.g., `7d` for 7 days, `30d` for 30 days, etc.). If a model instance or its training data has not been used for inference or refitting within this period, it will be considered stale and eligible for cleanup.
`ttl` defines the time-to-live period for model instances. It should be a valid period string (e.g., `7d` for 7 days or `30d` for 30 days). If a model instance has not been used for inference or refitting within this period, it is considered stale and eligible for cleanup.
> If `ttl` is greater than a scheduler's `fit_every`, the model is refitted before it becomes stale and the TTL has no effect.
`check_interval` argument defines how often the service should check for stale artifacts. It should be a valid period string (e.g., `1h` for 1 hour, `24h` for 24 hours, etc.). During each check, the service will evaluate all stored model instances and their training data against the defined `ttl` and remove those that are stale.
`check_interval` defines how often the service should check for stale artifacts. It should be a valid period string (e.g., `1h` for 1 hour or `24h` for 24 hours). During each check, the service evaluates stored model instances against the defined `ttl` and removes those that are stale.
> Check interval should be set to a value smaller than `ttl` and smaller than the smallest `fit_every` period among all schedulers used in the config to ensure timely cleanup of stale artifacts, otherwise stale artifacts may persist longer than intended.
### Example
Here's an example configuration that enables retention with a TTL of 1 day and a check interval of 30 minutes, where inference is performed every 15 minutes.
- Model instances and their training data that have not been used for inference or refitting within the last day will be cleaned up every 30 minutes (m2 example on a diagram)
- Model instances that have not been used for inference or refitting within the last day will be cleaned up every 30 minutes (m2 example on a diagram)
- While model instances used for inference within the last day at least 1 time will be retained (m1 example on a diagram)
![Retention Example Diagram](vmanomaly-ttl-example.webp)
@@ -382,7 +401,7 @@ settings:
# other settings
restore_state: True # enables state restoration
retention:
ttl: 24h # time-to-live for model instances and their training data
ttl: 24h # time-to-live for inactive model instances
check_interval: 30m # interval to check for stale artifacts
```

View File

@@ -1,5 +1,6 @@
---
title: Writer
description: "Data writer. Write anomaly scores back to VictoriaMetrics."
weight: 4
menu:
docs:

View File

@@ -1,5 +1,6 @@
---
title: Guides
description: "Step-by-step guides for deploying, configuring, integrating, and operating vmanomaly for anomaly detection."
weight: 3
menu:
docs:

View File

@@ -10,9 +10,9 @@ sitemap:
- To use *vmanomaly*, part of the enterprise package, a license key is required. Obtain your key [here](https://victoriametrics.com/products/enterprise/trial/) for this tutorial or for enterprise use.
- In the tutorial, we'll be using the following VictoriaMetrics components:
- [VictoriaMetrics Single-Node](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) (v1.149.0)
- [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/) (v1.149.0)
- [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/) (v1.149.0)
- [VictoriaMetrics Single-Node](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) (v1.150.0)
- [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/) (v1.150.0)
- [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/) (v1.150.0)
- [Grafana](https://grafana.com/) (v12.2.0)
- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/)
- [Node exporter](https://github.com/prometheus/node_exporter#node-exporter) (v1.9.1) and [Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/) (v0.28.1)
@@ -124,12 +124,12 @@ Detailed parameters in each section:
* `schedulers` ([PeriodicScheduler](https://docs.victoriametrics.com/anomaly-detection/components/scheduler/#periodic-scheduler) is used here)
* `infer_every` - Specifies the frequency at which the trained models perform inferences on new data, essentially determining how often new anomaly score data points are generated. Format examples: 30s, 4m, 2h, 1d (time units: 's' for seconds, 'm' for minutes, 'h' for hours, 'd' for days). This parameter essentially asks, at regular intervals (e.g., every 1 minute), whether the latest data points appear abnormal based on historical data.
* `fit_every` - Sets the frequency for retraining the models. A higher frequency ensures more updated models but requires more CPU resources. If omitted, models are retrained in each `infer_every` cycle. Format is similar to `infer_every`.
* `fit_every` - Sets the frequency for retraining the models. [Online models](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-models) learn from every inference batch, so set a large value such as `1000d` to make fitting effectively bootstrap-only. For evolving behavior, configure the model's forgetting or reactivity mechanism, or choose a finite fit cadence to reset accumulated state. Format is similar to `infer_every`.
* `fit_window` - Defines the data interval for training the models. Longer intervals allow for capturing extensive historical behavior and better seasonal pattern detection but may slow down the model's response to permanent metric changes and increase resource consumption. A minimum of two full seasonal cycles is recommended. Example format: 3h for three hours of data.
* `models`
* `class` - Specifies the model to be used. Options include custom models ([guide here](https://docs.victoriametrics.com/anomaly-detection/components/models/#custom-model-guide)) or a selection from [built-in models](https://docs.victoriametrics.com/anomaly-detection/components/models/#built-in-models), such as the [Facebook Prophet](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet) (`model.prophet.ProphetModel`).
* `args` - Model-specific parameters, formatted as a YAML dictionary in the `key: value` structure. Parameters available in [FB Prophet](https://facebook.github.io/prophet/docs/quick_start) can be used as an example.
* `class` - Specifies the model to be used. Options include custom models ([guide here](https://docs.victoriametrics.com/anomaly-detection/components/models/#custom-model-guide)) or a selection from [built-in models](https://docs.victoriametrics.com/anomaly-detection/components/models/#built-in-models). For operational metrics with calendar behavior, use the online [Temporal Envelope](https://docs.victoriametrics.com/anomaly-detection/components/models/#temporal-envelope).
* Model-specific parameters are configured directly below the model alias, as shown in the example.
* `reader`
* `datasource_url` - The URL for the data source, typically an HTTP endpoint serving `/api/v1/query_range`.
@@ -145,16 +145,16 @@ Below is an illustrative example of a `vmanomaly_config.yml` configuration file.
schedulers:
periodic:
infer_every: "1m"
fit_every: "1h"
fit_window: "2d" # 2d-14d based on the presence of weekly seasonality in your data
fit_every: "1000d" # bootstrap-only schedule; use a finite cadence if accumulated state must be reset
fit_window: "14d" # two weekly cycles for initial bootstrap
models:
prophet:
class: "prophet"
args:
interval_width: 0.98
weekly_seasonality: False # comment it if your data has weekly seasonality
yearly_seasonality: False
temporal_envelope:
class: "temporal_envelope"
alpha: 0.005 # adapt the trend while using the bootstrap-only fit schedule
loss_reactivity: 5 # allow new deviations to update the envelope
seasonalities: ["hod_smooth", "dow_smooth"]
provide_series: ["anomaly_score", "y", "yhat", "yhat_lower", "yhat_upper"]
reader:
datasource_url: "http://victoriametrics:8428/"
@@ -279,19 +279,24 @@ global:
scrape_configs:
- job_name: 'vmagent'
static_configs:
- targets: ['vmagent:8429']
- targets:
- 'vmagent:8429'
- job_name: 'vmalert'
static_configs:
- targets: ['vmalert:8880']
- targets:
- 'vmalert:8880'
- job_name: 'victoriametrics'
static_configs:
- targets: ['victoriametrics:8428']
- targets:
- 'victoriametrics:8428'
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
- targets:
- 'node-exporter:9100'
- job_name: 'vmanomaly'
static_configs:
- targets: [ 'vmanomaly:8490' ]
- targets:
- 'vmanomaly:8490'
```
@@ -323,7 +328,7 @@ Let's wrap it all up together into the `docker-compose.yml` file.
services:
vmagent:
container_name: vmagent
image: victoriametrics/vmagent:v1.149.0
image: victoriametrics/vmagent:v1.150.0
depends_on:
- "victoriametrics"
ports:
@@ -340,7 +345,7 @@ services:
victoriametrics:
container_name: victoriametrics
image: victoriametrics/victoria-metrics:v1.149.0
image: victoriametrics/victoria-metrics:v1.150.0
ports:
- 8428:8428
volumes:
@@ -373,7 +378,7 @@ services:
vmalert:
container_name: vmalert
image: victoriametrics/vmalert:v1.149.0
image: victoriametrics/vmalert:v1.150.0
depends_on:
- "victoriametrics"
ports:
@@ -387,7 +392,7 @@ services:
- "--notifier.url=http://alertmanager:9093/"
- "--rule=/etc/alerts/*.yml"
# display source of alerts in grafana
- "--external.url=http://127.0.0.1:3000" #grafana outside container
- "--external.url=http://127.0.0.1:3000" # grafana outside container
# when copypaste the line be aware of '$$' for escaping in '$expr'
- '--external.alert.source=explore?orgId=1&left=["now-1h","now","VictoriaMetrics",{"expr": },{"mode":"Metrics"},{"ui":[true,true,true,"none"]}]'
networks:
@@ -395,7 +400,7 @@ services:
restart: always
vmanomaly:
container_name: vmanomaly
image: victoriametrics/vmanomaly:v1.30.1
image: victoriametrics/vmanomaly:v1.30.2
depends_on:
- "victoriametrics"
ports:

Some files were not shown because too many files have changed in this diff Show More