Storage status — operator guide#

The Storage page (/console/storage) and the Cluster Overview Storage hero card surface the live health of the meta + data backends and the per-storage-class object distribution. This page covers the env vars the UI reads through, what each warning means, and how to interpret the RADOS / TiKV / Cassandra-specific signals.

The page is fed by three small admin endpoints:

EndpointReturnsPolled by
GET /admin/v1/storage/metaMetaHealthReport (Cassandra peers / TiKV PD stores / memory)/storage Meta tab @ 30 s
GET /admin/v1/storage/dataDataHealthReport (RADOS pool stats / S3 reachability / memory)/storage Data tab @ 30 s
GET /admin/v1/storage/classes{classes:[…], pools_by_class:{…}}/storage Data tab @ 30 s, Cluster Overview hero @ 60 s
GET /admin/v1/storage/healthaggregate {ok, warnings, source}<StorageDegradedBanner> @ 30 s on every authed page

Schema details live in internal/adminapi/openapi.yaml.

Backend selection#

VariableDefaultPurpose
STRATA_META_BACKENDmemoryOne of memory, cassandra, tikv. Drives which meta.HealthProbe the Meta tab reports.
STRATA_DATA_BACKENDmemoryOne of memory, rados, s3. Drives which data.HealthProbe the Data tab reports.

Memory backends always report a single in-process row — there is no network topology to surface. The page renders an explainer card instead of an empty table.

Cassandra (Meta tab)#

The Cassandra probe issues two LOCAL_ONE queries (CQL has no UNION ALL):

SELECT peer, data_center, rack, release_version, schema_version
  FROM system.peers;
SELECT broadcast_address, data_center, rack, release_version, schema_version
  FROM system.local;

Results are merged Go-side into one []NodeStatus. Replication factor reads from system_schema.keyspaces. Results are cached in-process for 10 s so adminapi pollers absorb bursts without re-querying.

WarningMeaning
schema versions disagree: …Two or more nodes report different system.schema_version. Ride it out during a rolling upgrade; investigate if persistent.
keyspace not found: <ks>The configured STRATA_CASSANDRA_KEYSPACE is missing — bootstrap likely never ran.
replication factor unknownsystem_schema.keyspaces returned no rows for the keyspace; same root cause.

Relevant env vars (full list in ScyllaDB backend): STRATA_CASSANDRA_HOSTS, STRATA_CASSANDRA_KEYSPACE, STRATA_CASSANDRA_LOCAL_DC, STRATA_CASSANDRA_SLOW_MS.

TiKV (Meta tab)#

A thin bootstrap-only HTTP client lives at internal/meta/tikv/pdclient.go. It tries each PD endpoint in order with a 2 s per-endpoint timeout and returns the first non-error response from GET /pd/api/v1/stores. Cross-PD failover for the data path is owned by tikv/client-go, so the probe deliberately does not refresh /pd/api/v1/members — bootstrap-only.

WarningMeaning
raft leader imbalance: store <id> has 0 leadersSome live TiKV store carries no Raft leaders while peers do. Usually transient during a rolling restart; persistent imbalance suggests a region-scheduler stall.
pd endpoints unreachable: …Every configured PD endpoint timed out / returned non-200. The data path is likely down too.

Relevant env vars (full list in TiKV backend): STRATA_TIKV_PD_ENDPOINTS.

RADOS (Data tab)#

The RADOS probe iterates the configured [rados] classes map (from STRATA_CONFIG), groups unique (cluster, pool, ns) tuples, and reuses the same *goceph.IOContext the gateway data path opens via internal/data/rados/backend.go::ioctx. Per-pool stats come from (*IOContext).GetPoolStats() (Num_kb*1024BytesUsed, Num_objectsChunkCount — RADOS chunk count, not S3 object count). Cluster-wide health uses (*Conn).MonCommand({"prefix":"status","format":"json"}) per unique cluster.

HEALTH_OK from ceph status suppresses warnings entirely. Anything else surfaces the headline status string plus up to 5 sorted check summaries:

WarningMeaning
HEALTH_WARN: Reduced data availability: 2 pgs inactiveCommon during recovery; see Ceph health checks for the full table.
HEALTH_ERR: 1 stale pgsOperator action required — typically OSD outage.
pool stats failed for <pool>: …Per-pool stat call errored; the row degrades to state=error and the report continues for sibling pools.

Per-pool degraded-object counts (parsed from ceph osd df JSON) are a deliberate future P3 — the current cycle ships headline-only.

S3-over-S3 (Data tab)#

The S3 backend probe runs HeadBucket on the configured backend bucket and reports state=reachable or state=error. BytesUsed and ChunkCount are left at 0 because the upstream S3 API does not expose them in O(1); the per-class breakdown (/admin/v1/storage/classes) covers the Strata-side dimensions.

Per-storage-class breakdown#

Per-class bytes and objects are produced by the bucketstats sampler (internal/bucketstats/sampler.go). The sampler walks every bucket on Interval (default 1 h, override via STRATA_BUCKETSTATS_INTERVAL) and publishes the cluster-wide totals to a shared *Snapshot consumed by /admin/v1/storage/classes. Per-(bucket, class) gauges (strata_storage_class_bytes, strata_storage_class_objects) are also emitted, capped at top-N buckets (STRATA_BUCKETSTATS_TOPN, default 100).

pools_by_class in the response carries the static class → backend pool mapping configured under [rados] classes. It is empty for memory and s3 backends; the UI hides the → pool chip suffix in those cases.

VariableDefaultPurpose
STRATA_BUCKETSTATS_INTERVAL1hSampler tick cadence (Go duration). E2E sets 500ms; production defaults are fine for the storage page.
STRATA_BUCKETSTATS_TOPN100Per-(bucket, class) gauge cardinality cap. The cluster-wide totals are unaffected.

A fresh gateway emits one initial sample pass during Sampler.Run so the hero card and /storage page populate without waiting an hour for the first tick.

Aggregate health + degraded banner#

/admin/v1/storage/health folds the meta + data probes into a single {ok, warnings, source} payload. ok=false if either probe returns warnings, or if any node / pool is in a non-OK state. The banner component (web/src/components/StorageDegradedBanner.tsx) polls this endpoint every 30 s on every authed page and renders above the AppShell when ok=false. Dismissal is keyed on a stable signature <source>|<sorted-warnings> so a fresh distinct degraded condition re-alerts even if the previous one was dismissed for the session.

STRATA_STORAGE_HEALTH_OVERRIDE is the e2e knob: set to a JSON-shaped override and the handler returns it verbatim. Invalid JSON is logged at WARN and falls through to the live probe so a misconfigured env-var does not blank the banner permanently. Don’t ship this in production.