Files
rustfs/docs/operations/storage-metrics.md
T
Zhengchao An 2909b1bfe1 fix(e2e): require a verified server binary for every e2e run (#7687)
* fix(ci): share quick checks and lint workflows

* fix(ci): install actionlint from its verified release

* fix(ci): reject dependencies on required quick checks

* feat(test): verify the E2E server build and source identity

* test(e2e): register verified Darwin test membership

* test(e2e): record verified Linux receipt test membership

* test(e2e): record compiled Darwin receipt test membership

* test(e2e): record compiled Linux receipt test membership

* test(e2e): record Darwin e2e-full membership after merging main

* fix(test): route scanner/heal evidence E2E runs through the verified server binary

The evidence runners built rustfs with plain cargo and then ran e2e_test directly, which now fails without a run receipt. They build through scripts/e2e_binary.py and run the e2e_test invocations under e2e_binary.py run; the obsolete rustfs.features stamp is removed.

* docs(e2e): run server-backed e2e commands through the verified binary wrapper

* test(e2e): record Linux e2e-full membership from the branch CI listing
2026-09-12 11:23:06 +08:00

145 lines
7.8 KiB
Markdown

# Storage metrics and observer selection
**Use this when:** configuring storage dashboards, migrating from replicated
per-drive metrics, or diagnosing stale and missing disk observations.
## Ownership and identity
The storage collectors have two distinct scopes:
| Scope | Metric families | Meaning |
|---|---|---|
| `collection_scope="local"` | `rustfs_system_drive_*`, `rustfs_node_disk_*` | Detailed metrics for drives owned by the reporting node; includes configured offline slots. Drive counts are local counts. |
| `collection_scope="cluster"` | Cluster capacity, health, objects/buckets, erasure sets, and `rustfs_cluster_drive_*` | One reporting node's observation of the whole cluster. |
`observer` identifies the reporting node. `server` identifies the drive owner;
`drive` alone is not a unique disk key. Keep `rustfs_cluster_id`, `server`, and
`drive` when grouping drives, and retain pool/set/drive indices for topology.
Drive counters also carry `disk_id`, so a physical replacement starts a separate
series. Unknown IDs are empty; `rustfs_system_drive_info` is emitted only when
the ID and topology are known. `rustfs_system_drive_present` preserves a configured
slot even when its disk is disconnected and its ID is unavailable.
Global drive metrics contain membership, runtime state, capacity and its source.
They do not duplicate remote API counters, error counters, or detailed I/O metrics.
An unreachable peer remains in the global inventory with the storage layer's
unknown/offline state and stale/missing capacity provenance. Missing capacity is
omitted from per-drive byte metrics. Cluster capacity may still include cached
observations: check `capacity_stale_drives` and `capacity_missing_drives` before
interpreting it. The existing cluster offline count includes drives not currently
observed online; use global per-drive runtime states to distinguish unknown.
## Configure the pipeline
Give every node of a deployment the same stable, unique resource attribute:
```bash
OTEL_RESOURCE_ATTRIBUTES=rustfs.cluster.id=production-a
```
Keep existing resource attributes in the comma-separated value. The example
Compose files accept `OTEL_RESOURCE_ATTRIBUTES` and default to the development
cluster ID `rustfs-dev`; set a distinct ID for each deployment. The Collector's
`resource_to_telemetry_conversion.enabled: true` promotes it to
`rustfs_cluster_id` on scraped samples. Prometheus `external_labels` are not a
substitute: they do not add a cluster label to the local time series.
Use the shipped Collector configuration with `send_timestamps: true` and keep
Prometheus's default `honor_timestamps: true`. Load
`.docker/observability/prometheus-rules/rustfs-storage.yml`. Use one ingestion
route per deployment/node; scraping replicas of the same Collector requires a
separate HA deduplication policy. Keep node and Prometheus clocks synchronized.
Storage export callbacks read the current snapshot from memory and stop exporting
removed series. They do not probe storage or run peer RPCs. A successful collection
updates `rustfs_storage_snapshot_last_success_timestamp_seconds`; OTLP export
alone does not update this value. The source age limit is three times the larger
of the collection interval and `RUSTFS_OBS_METER_INTERVAL` (with its normal default).
`rustfs_storage_snapshot_max_age_seconds` exports the remaining validity budget
after deducting collection time from that limit. A slow RPC or usage read therefore
cannot make an already expired observation fresh by completing. An unavailable
source leaves the last success unchanged; a stalled collector stops exporting its
old snapshot after the source age limit.
A Collector may cache a point after RustFS stops exporting it. The recording rules
therefore require both a fresh source and a raw point timestamp at least as new as
that source's last successful collection. This also removes cached optional fields
and old disk IDs when a new snapshot no longer contains them. The timestamp check
must run on the raw selector, before label rewriting or recording the value;
otherwise PromQL can substitute the query evaluation time for the original sample
time. The comparison uses Prometheus millisecond precision so co-published points
are not excluded by submillisecond rounding. Current recording rules preserve the
original name in `source_metric`.
When adding a storage metric, add its matching rule to that file.
Collection and export are asynchronous, not a transaction across instruments. A
snapshot published during an export can briefly mix adjacent observations or
withhold a value; the next complete export/collection converges. Source failure visibility is bounded
by the published maximum age plus the scrape/rule intervals. Cached capacity has
its own observation age and state; a fresh collection does not make that capacity
live. Bucket/object counts retain the existing scanner update delay.
## Queries and dashboards
The bundled dashboard requires the storage recording rules. Select one **Storage
cluster** and one fresh **Cluster observer**. Global panels use that observer's
complete view. If it expires, the panel shows no data; select another observer.
Do not sum replicated global totals or independently take maxima/minima of fields
from different observers: they may describe different moments or partitions.
For example, select the observed cluster raw capacity:
```promql
rustfs:storage:current{source_metric="rustfs_cluster_capacity_raw_total_bytes",rustfs_cluster_id="production-a",collection_scope="cluster",observer="node1:9000"}
```
For a per-drive API rate, select owner-scoped counters before `rate`, then require
a current observation. Sum the resulting rates only across the desired drives:
```promql
sum by (rustfs_cluster_id, api) (
rate(rustfs_system_drive_api_calls_total{rustfs_cluster_id="production-a",collection_scope="local"}[5m])
and ignoring(source_metric)
rustfs:storage:current{source_metric="rustfs_system_drive_api_calls_total",rustfs_cluster_id="production-a",collection_scope="local"}
)
```
A missing series is unknown, not zero. Local totals include only fresh owners and
can decrease when a node becomes unreachable. Use a selected global observer for
configured topology and its explicit unknown/stale indicators.
## Rolling upgrades and validation
Install the rules, resource attribute, and dashboard together. During rolling
upgrades, old releases have no `collection_scope`; strict new-scope selectors
exclude their replicated series. Local panels initially contain only upgraded
owners. Global panels become available when an upgraded observer publishes a fresh
snapshot. Historical old-label series remain available for retrospective queries.
Do not combine old and new counter histories into one rate.
Run the PromQL regression fixtures from `.docker/observability/tests`:
```bash
promtool test rules storage-rules.test.yml
```
The native pipeline test in
`crates/e2e_test/src/storage_metric_ownership_test.rs` requires pinned Collector,
Prometheus, previous-release RustFS, and current RustFS executables. Set
`RUSTFS_OTELCOL_BINARY`, `RUSTFS_PROMETHEUS_BINARY`, and
`RUSTFS_METRICS_BASELINE_BINARY` to those files; `scripts/e2e_binary.py run`
supplies the current RustFS as `CARGO_BIN_EXE_rustfs`.
Optionally set `RUSTFS_METRICS_E2E_ARTIFACTS` to retain logs and Prometheus data.
Run only this external-tool test:
```bash
python3 scripts/e2e_binary.py build
python3 scripts/e2e_binary.py run -- cargo test --locked -p e2e_test storage_metric_ownership_pipeline -- --ignored --nocapture
```
The test first reproduces duplicated global details with the previous release,
rolls four nodes forward, verifies owner and disk identity, and checks node loss
and recovery while the Collector remains running. SDK unit tests cover removed
series, replacements, counter resets, invalid updates, and a stalled collection;
PromQL fixtures cover shared paths across clusters/pools and stale cached fields.