novadocs
Operations

Operations

Metrics, dashboards, and logs

The metrics listener, the metric families that matter, the shipped Grafana dashboards, what to alert on, and how novad logs

Every novad exposes one ops listener carrying Prometheus metrics, the health probes, pprof, and the admin dashboard. This page names the metrics worth a panel, the dashboards that already have them, the one alerting principle, and what the log says at each level.

The metrics listener

--metrics-listen <addr> (NOVAD_METRICS_LISTEN) serves /metrics; empty disables it and with it /healthz, /readyz, /debug/pprof/, and /admin/. The compose stack publishes it as localhost:9191 through 9193; the Fly and Kubernetes deployments scrape port 9090. novagateway has the same flag, defaulting to :9090.

Instruments are declared in OpenTelemetry dotted form (nova.append.bytes) and exported to Prometheus with unit suffixes: . becomes _, an s-unit histogram becomes _seconds (_bucket, _sum, _count), a By unit becomes _bytes, and counters gain _total. So nova.auditor.due.age is nova_auditor_due_age_seconds and nova.write.bytes is nova_write_bytes_total. The per-node dimension is Prometheus's own instance label. The gRPC RED metrics (rpc_server_call_duration_seconds) come from the otelgrpc stats handler.

The metric families

Names below are the OpenTelemetry form; apply the export rule.

Append and write path

MetricMeaning
nova.write.latencydispatch admission to durable ack, per class: the uniform write round-trip
nova.write.bytesdurably acked payload bytes, per class
nova.write.errorsfailed writes: dispatch refusals and failed durability waits
nova.append.bytescumulative committed payload bytes; rate() is append throughput
nova.direct.pool.waitper-batch wait from admission to its flight's cut: the pool queue ahead of the publisher
nova.direct.publish.durationone flight's publish as the flusher sees it
nova.direct.flush.duration, nova.direct.flush.bytesper-flush publish latency (build plus slot PUT) and bundle size
nova.direct.commit.durationper-flight commit: slot PUT through ordered ack release
nova.direct.pending.bytes, nova.direct.pending.limit.bytesbytes held against the admission budget, and the cap admission blocks at
nova.dataplane.ack.sendone ack's wire write; a spike here with flat write.latency is a wire stall on this star
nova.dataplane.shedrequests shed under backpressure, by resource (session, subscription, capacity)
nova.dataplane.sessionsconcurrent producer sessions and subscriptions, by resource
nova.dataplane.orphaned.leaseorphaned-lease heals; flat zero is the only healthy reading

Chain, backlog, and absorb

MetricMeaning
nova.chain.backlog.ageage of the oldest flight in the leader's flight index: the write-side age signal
nova.chain.backlog.flights, nova.chain.backlog.bytesbacklog fill
nova.chain.backlog.stallpublishes that blocked on backlog tokens, by diagnosed cause (absorb_lag, metastore_slow, settling)
nova.direct.backlog.waitper-flight wait for a backlog token; nonzero means the absorb's checkpoint is pacing producers
nova.chain.slot.duration, nova.chain.boarding.durationslot commit latency, and the pre-PUT wait between token and slot acceptance
nova.chain.roll.count, nova.chain.roll.waitingepoch turnovers by outcome; roll.waiting at 1 means appends are failing fast on the roll pacer
nova.commit.gate.holdsopens refused while a prior chain epoch is unsettled
nova.absorb.pass.duration, nova.absorb.pass.roundsone absorb pass's wall clock and batches; sustained multi-round passes mean the drain is chasing ingest
nova.absorb.batch.slots, nova.absorb.batch.streams, nova.absorb.phase.durationper-batch amortization and phase timing
nova.absorb.wake, nova.absorb.feed, nova.absorb.memo.misswake-ups by reason, feed outcomes, memo misses
nova.writer.limbo.streams, nova.writer.limbo.agestreams departed uncertified with their lease retained; a growing age means coverage cannot land
nova.writer.lease.release.failedreleases parked for the retry sweep: a metastore-storm signal

Read and subscribe

MetricMeaning
nova.read.latencyread latency by source: the tail window (hot) versus object storage
nova.tailwindowcache.hits, .served.bytes, .fallbacks, .evictions, .skipped, .resident.bytesthe leader's in-memory tail window: what it served, what fell to ranged GETs, budget pressure
nova.subscribe.parked, nova.subscribe.wakessubscriptions parked on the leader's wake, and wakes delivered
nova.subscribe.redirectssubscriptions redirected off this star, by cause (departure, frontier)
nova.dataplane.trim.redirectstrims answered NOT_OWNER off the holder; tracks lease churn, not error

Object storage and caches

MetricMeaning
nova.objectstore.durationoperation latency by operation (PUT/GET/LIST/DELETE) and store
nova.objectstore.bytesbytes transferred by operation (put, put_if_absent, get)
nova.objectstore.attempt.errorsfailed attempts inside the SDK retry loop, by operation and cause; a transient the retry recovers appears only here
nova.objectcache.requests, .fallbacks, .spills, .ejections, .ring.sizethe shared object cache: cache-routed requests, falls to direct by cause, hot-key spills, ejected instances, eligible instances
nova.fetchcache.hits, .downloads, .download.bytes, .fallbacks, .evictions, .prefetch.dropped, .resident.bytesthe auditor's whole-object fetch cache, when enabled

Auditor

MetricMeaning
nova.auditor.due.ageage of the oldest ready item per due queue: the backlog-age alert signal
nova.audit.jobs.armed, nova.audit.jobs.resolvedmarkers armed and resolved, by queue; read for flow balance, never alert on their rates
nova.auditor.step.durationone leased job's handle latency, by step
nova.auditor.lease.skipsclaims lost to a peer, by job type; climbing against a standing marker means the holder is wedged
nova.auditor.settle.spans, nova.auditor.settle.bytesspan settlements by outcome; bytes relocated into per-stream outputs (the rewrite budget's demand)
nova.auditor.group.members, .member.latency, .member.phasegroup settle width and per-member timing
nova.auditor.probe.verdictsreclamation probe judgments: stand, observe, confirm-reap, gc, gone
nova.auditor.garbage.reaped, nova.auditor.slots.trimmed, nova.auditor.teardown.gatedzombie slot copies deleted above the cut; slots trimmed; teardown walks re-armed behind the settle frontier

Fleet and node

MetricMeaning
nova.leader.countstreams this star leads
nova.node.utilization, nova.node.utilization.axisbottleneck utilization (saturated above 0.85, relieved below 0.70) and the per-axis breakdown
nova.node.saturated, nova.node.stranded1 while at a ceiling and refusing leadership; 1 while no live peer can absorb a led stream
nova.node.handoffscumulative load-balance handoffs
nova.kv.durationmetastore point-operation latency

The Gateway exports nova.gateway.vended.bytes and .proxied.bytes (the offload ratio), .fetch.wait, .fetch.inflight.bytes, .replans, .seam.handoffs, and the footer cache's hits and evictions.

The Grafana dashboards

dashboards/ holds the JSON; the compose stack provisions it, the Kubernetes controller ships it as sidecar ConfigMaps when observability.dashboards is on, and it imports into any Grafana by hand.

Nova — Streaming Log Store (nova-overview) is the single pane: Overview (streams led, stars saturated, append p99, the oldest reclamation backlog, traffic distribution, utilization by star, sheds), Write path, Read path (latency and rate by source), Tiering — object storage (op latency and rate, bytes, average bytes per PUT), Chain — backlog, absorb & turnover (backlog age marked alert on this, resident flights and bytes, stalls by cause, admission wait, slot commit latency, epoch turnovers), Metadata — oxia, Auditor — background reclamation (backlog age by queue marked alert on this, step latency and rate, jobs armed and resolved, garbage reaped, probe verdicts, slots trimmed), Auditor — settle fetch cache, RPC — gRPC, Reliability — recovery & sheds, and Runtime.

Nova — Oxia Metastore (nova-oxia) follows a ref commit through the metastore — client latency as Nova sees it, leader and follower write latency, batch commit, WAL sync, pebble compaction — and graphs each server resource against its provisioned ceiling, so "which layer is the bottleneck" is a glance.

Nova — Object Cache (nova-objectcache) shows the cache tier's effectiveness (hit ratio, S3 traffic saved), residency, and health.

The one alert to wire first

nova_auditor_due_age_seconds is the age of the oldest past-due reclamation item. A throughput graph looks healthy while a queue silently grows; a rising age does not. Alert when it climbs past your grace windows (minutes to warn, hours to page).

The principle generalizes: alert on backlog age, never rate. A full backlog at any fill turns over within seconds and is the normal full-rate condition; minutes of standing age mean the chain is wedged or chronically overdriven. The same rule applies to nova_chain_backlog_age_seconds, nova_writer_limbo_age_seconds, and nova_gateway_fetch_wait_seconds. The Kubernetes controller ships rules for exactly these (fleet and node saturation, stranded, per-class write SLOs, writer-limbo age, rewrite-backlog age).

Beside age, three fleet signals deserve rules: nova_node_stranded at 1 (capacity, now), a sustained rate of nova_dataplane_shed_total{resource="capacity"} (producers being refused), and a sustained rate of nova_node_handoffs_total (the fleet rebalancing constantly instead of settling).

Logging

novad writes structured logs through slog. Three levels, one rule each:

LevelCarries
InfoLifecycle edges, one line per state transition of a resource (stream, bucket, credential, leadership, chain epoch, settle walk, the star's own drain, saturation, and registration state, each close phase), and the star summary. An edge logs the change, never the steady state.
DebugPer-connection and per-job lines: producer sessions and subscriptions (caller, peer, plane, bytes, duration), auditor jobs. Debug because they scale with client churn, not with the fleet.
Warn / ErrorFailures and sheds. Every limit that sheds or truncates logs a line; silent shedding reads as healthy.

Keys are snake_case, the error key is err, durations render as strings, and every message opens with its package prefix (novad:, writer:, auditor:). --log-level (NOVAD_LOG_LEVEL) takes debug, info, warn, or error; default info.

The star summary

One Info line per --log-summary-interval (default 1m0s; 0 disables), the log's proof of life and load between edges, written even when the star is idle:

novad: star summary leaders=… sessions=… subscriptions=… append_mibps=… offered_mibps=… read_mibps=…
  backlog_standard_age=… backlog_express_age=… retained_mib=… utilization=… axis=…
  draining=… drained=… saturated=… stranded=… auditor_jobs=… auditor_failed=…

Rates are MiB/s over the interval (offered is what producers submitted; append is what the chain acked; read is hot-tail bytes served); backlog_<class>_age is per configured class; retained_mib sums the admission budgets, the tail window cache, and session transport; axis names the resource behind utilization. The auditor fields appear only when one runs in the process.

pprof and health

/debug/pprof/ rides the same listener. A star that stalls without erroring is usually backpressure; the goroutine dump names the blocked stage:

curl -s localhost:9191/debug/pprof/goroutine?debug=1 | less
go tool pprof "localhost:9191/debug/pprof/profile?seconds=10"

/healthz is liveness (process alive, no dependency checks); /readyz is readiness (registered, not draining). Node lifecycle has the exact answers; the admin dashboard is at /admin/ on the same port.

Source documents

  • dashboards/README.md, dashboards/nova-overview.json, dashboards/nova-oxia.json, dashboards/nova-objectcache.json
  • internal/daemon/summary.go, internal/daemon/stats.go, internal/daemon/observability.go
  • docs/design/000-decisions.md (lifecycle logging entry)

On this page