Monitoring & Observability
VeltrixDB exposes a Prometheus scrape endpoint (about 85 veltrixdb_* metric families), liveness/readiness probes, a slow-trace endpoint and a web dashboard, all on the same admin HTTP port.
Endpoints
curl http://localhost:2112/metrics # Prometheus scrape endpoint
curl http://localhost:2112/healthz # liveness probe ("ok")
curl http://localhost:2112/readyz # readiness probe ("ready" / 503)
curl http://localhost:2112/traces # recent sampled + slow (≥ 50 ms) spans, NDJSON
open http://localhost:2112/admin/ui # web dashboard (loopback-only unless -admin-token)
All of these are served on the admin/metrics port (-metrics-addr, :2112 by default), separate from the client data port (9000 by default). /metrics, /healthz and /readyz are never authenticated. /readyz answers 503 initializing until the engine is open and 503 degraded: disks [N] failed when a disk breaker has tripped; it does not wait for WAL replay or the search-index rebuild — check INFO for search_ready=1 before sending searches to a restarted node.
veltrixdb_* series are exported. The server registers its own collector on a private registry: there are no go_* runtime or process_* metrics, and the runtime / process / system / disk collector in metrics/hardware.go is not registered either. Get process and host metrics from node-exporter or a process exporter. Search and raft have no Prometheus metrics; search state is in INFO (search_ready, search_loaded=x/y), raft state in /admin/cluster, and both in the logs.New on main after v1.16 — ships in the next release
/metrics also serves go_* and process_* (labelled node; process_* reads /proc, so it is Linux only — macOS serves only process_start_time_seconds), the hardware collector the Grafana dashboards read (veltrixdb_runtime_*, _process_*, _system_*, _disk_*; the last three Linux only), and the search, raft and replication-recovery series below. A scrape of a fresh node takes about 8 ms.
Key metrics to watch
All metric families carry a node label (per-disk ones add disk). The core set to alert on:
| Metric | Healthy value |
|---|---|
veltrixdb_storage_writes_total / veltrixdb_storage_reads_total | Growing under load |
veltrixdb_cache_hits_total / veltrixdb_cache_misses_total | Hit rate > 90% |
veltrixdb_storage_write_admission_throttles_total | 0 |
veltrixdb_vlog_garbage_ratio | < 0.30 |
veltrixdb_vlog_gc_emergency_runs_total | 0 |
veltrixdb_scrub_corruption_total / veltrixdb_vlog_gc_read_errors_total | 0 (any increase = suspect disk) |
veltrixdb_storage_transform_metadata_damaged | 0 (non-zero = run veltrix-repair) |
veltrixdb_vlog_blkdiscard_errors_total | 0 (raw VLog without CAP_SYS_RAWIO) |
Other useful series (every name below is defined in metrics/prometheus.go):
| Metric | What it shows |
|---|---|
veltrixdb_storage_writes_total / veltrixdb_storage_reads_total / veltrixdb_storage_deletes_total | Per-node operation counters (as scraped with the node label) |
veltrixdb_storage_write_latency_seconds / veltrixdb_storage_read_latency_seconds / veltrixdb_storage_delete_latency_seconds | Latency histograms — source for P50/P95/P99 via histogram_quantile |
veltrixdb_storage_wal_flushes_total | WAL group-commit fdatasync rate — compare against writes/s for amortization ratio |
veltrixdb_vlog_writes_total | VLog append rate |
veltrixdb_cache_hit_rate / veltrixdb_cache_size_bytes / veltrixdb_cache_max_size_bytes | LIRS cache hit rate and RAM used vs. capacity |
veltrixdb_cache_evictions_total (labeled type=lirs|ttl|defrag) | Eviction pressure by cause |
veltrixdb_vlog_file_bytes | VLog size per NVMe disk — also the numerator for capacity planning against PVC size |
veltrixdb_vlog_gc_runs_total / veltrixdb_vlog_gc_bytes_total | Background GC activity |
veltrixdb_storage_index_size_keys | In-memory index size (keys held in DRAM) |
veltrixdb_storage_native_index_bytes New in v1.6 | Bytes held off the Go heap by the native C++ index (0 when the Go map index is in use). It is C memory outside the Go heap, so it is not in go_memstats_* (which v1.16 and earlier do not export at all); compare it with the container's RSS when sizing memory |
veltrixdb_vlog_disk_slow / veltrixdb_vlog_disk_read_latency_ewma_seconds | Per-disk slow-disk flag and smoothed read latency |
veltrixdb_scrub_corruption_total | CRC32C mismatches found by the background scrubber — should stay 0 |
veltrixdb_cluster_nodes_total | Cluster topology size, for multi-node deployments |
veltrixdb_failure_detector_nodes_failed_total / _nodes_recovered_total | Failed minus recovered over a window = peers still down (there is no "currently failed" gauge) |
veltrixdb_server_active_connections, veltrixdb_server_network_bytes_in_total / _out_total | Client connections and traffic |
veltrixdb_storage_atomic_ops_total | Successful CAS/INCR/DECR/SETNX |
veltrixdb_replication_lag_bytes, veltrixdb_replication_failures_total | Replicated mode only (absent in standalone and raft) |
veltrixdb_replication_lag_nanoseconds (nothing writes it; use lag_bytes or /admin/cluster), veltrixdb_cluster_partition_migrations_total (nothing increments it; use the [transfer] migration done moved=N log line) and veltrixdb_replication_conflict_resolutions_total / _vector_clock_updates_total (no conflict resolution runs). Fixed in v1.14 lag_nanoseconds is the age of the oldest write a replica has not acked (0 when caught up), partition_migrations_total counts key batches (≤ 500 keys) a new owner acknowledged, the two conflict / vector-clock counters are removed, and new series appear: veltrixdb_replication_replicas_failed / _replicas_sync_pending (gauges), _recovery_probes_total, _replica_recoveries_total and veltrixdb_failure_detector_nodes_reactivated_total (RECOVERING → ACTIVE). veltrixdb_cache_misses_total also counts lookups of keys that do not exist.
Useful PromQL for day-to-day operation:
rate(veltrixdb_storage_writes_total[1m]) # write throughput
rate(veltrixdb_storage_reads_total[1m]) # read throughput
rate(veltrixdb_cache_hits_total[5m]) /
(rate(veltrixdb_cache_hits_total[5m]) + rate(veltrixdb_cache_misses_total[5m])) # hit ratio
rate(veltrixdb_storage_writes_total[1m]) / rate(veltrixdb_storage_wal_flushes_total[1m]) # WAL amortization
histogram_quantile(0.99, rate(veltrixdb_storage_read_latency_seconds_bucket[5m])) # P99 read
Search metrics New on main after v1.16 — ships in the next release
Every mode. The coordinating node counts each client vector (VSEARCH, VSEARCHX), text (TSEARCH) and hybrid (HSEARCH) request once, fan-out included; peer-to-peer /internal/search calls are not counted. Recording a request allocates nothing.
| Metric | Meaning |
|---|---|
veltrixdb_search_requests_total{kind} | Requests per kind = vector | text | hybrid, failed ones included |
veltrixdb_search_errors_total{kind,reason} | rebuilding (refused during the index rebuild), incomplete (a peer did not answer the fan-out), other |
veltrixdb_search_latency_seconds{kind} | Latency on the coordinating node, fan-out included (0.5 ms – 5 s buckets) |
veltrixdb_search_partial_total | Searches answered without every peer (--search-allow-partial) |
veltrixdb_search_ready | 1 when the search indexes are complete; 0 while the startup or snapshot-restore rebuild runs |
veltrixdb_search_rebuild_loaded_keys / _rebuild_total_keys | Rebuild progress (the total stays 0 until the rebuild's scan finishes) |
veltrixdb_search_vectors / _vector_tombstones / _vector_namespaces, veltrixdb_search_namespace_vectors{namespace} | Live vectors, graph tombstones awaiting compaction, namespaces; per-namespace series for the first 64 namespaces only |
veltrixdb_search_text_documents / _text_namespaces | Indexed text documents and BM25 namespaces |
Raft and snapshot metrics New on main after v1.16 — ships in the next release
--mode=raft only, read from the raft node at scrape time. On v1.16 and earlier read the term and leader from GET /admin/cluster or veltrix nodes.
| Metric | Meaning |
|---|---|
veltrixdb_raft_term / _is_leader | Current term; 1 on the leader |
veltrixdb_raft_commit_index / _applied_index | Commit and apply position; the difference is how stale local reads are |
veltrixdb_raft_snapshot_index / _snapshot_term, _log_retained_entries, _snapshot_threshold_entries | Snapshot position, log kept after it, and --raft-snapshot-threshold |
veltrixdb_raft_snapshot_captures_total / _installs_total, _chunks_sent_total / _chunks_received_total, _legacy_sent_total | Captures, installs from a leader, InstallSnapshot chunks, single-shot snapshots sent to v1.16 and older followers |
veltrixdb_raft_snapshot_last_capture_seconds / _last_apply_fence_seconds / _last_bytes | Duration, Apply stall and file size of the last capture |
veltrixdb_raft_snapshot_capture_failures_total, _sent_total, _transfer_failures_total, _install_failures_total, _restore_failures_total | Failed captures; leader: completed and failed transfers (including a single-shot over 256 MB, NOT sending snapshot … upgrade peer); follower: failed installs and restore attempts |
Replication recovery metrics New on main after v1.16 — ships in the next release
--mode=replicated only. Per replica, the same state is in GET /admin/cluster as replication[].resync_pending and replication[].delete_gap. See Replication & CDC.
| Metric | Meaning |
|---|---|
veltrixdb_replication_full_resyncs_total | Catch-ups that streamed the whole change feed (watermark older than the tombstone retention, or replication_state.json unreadable; ERROR FULL RESYNC) |
veltrixdb_replication_catchup_gaps_total | Recoveries that could not replay everything the replica missed; deletes may be missing on it |
veltrixdb_replication_replicas_delete_gap / _replicas_resync_pending | Gauges: replicas flagged with a delete gap; replicas whose catch-up is pending or running |
veltrixdb_replication_state_persists_total / _state_persist_errors_total | Writes of replication_state.json (at most one per second), and failed ones |
Disk-failure auto-degrade
VeltrixDB detects failing disks automatically with a per-disk consecutive-error breaker: 5 consecutive I/O errors on a disk's WAL/VLog trip the breaker and mark the disk FAILED. Any successful I/O before the fifth error resets the streak, so transient hiccups don't trip it. Once tripped:
- Writes routed to that disk fail fast with
ErrDiskFailed("disk marked FAILED — node is degraded, retry on another replica") instead of hitting the dead device's timeouts. Clients should treat it as a retriable node-degraded condition and retry against another replica. - Reads still try the device — data may be partially readable — and are served from cache when possible.
- GC and compaction skip the failed disk.
/readyzreturns503with a body likedegraded: disks [2] failed, so orchestrators (Kubernetes readiness probes) stop routing new traffic to the node while reads keep being served.INFOreportsFAILED_DISKS=[2], and the engine's internalDiskFailurescounter records breaker trips (it is not exported as a Prometheus series; alert on/readyzorINFO).
Admin API
The same admin port also exposes a small JSON control-plane API, documented in full in the Admin API reference:
curl http://localhost:2112/admin/stats # engine snapshot (JSON)
curl http://localhost:2112/admin/version # schema version
curl -X POST http://localhost:2112/admin/checkpoint # force WAL checkpoint
curl http://localhost:2112/admin/cdc?prefix= # live CDC events (NDJSON)
curl http://localhost:2112/admin/cluster # topology: role, raft term/leader, peers, epoch, replica lag
curl http://localhost:2112/admin/cluster/members # membership (new on main after v1.16)
Grafana dashboards
The repository ships provisioned Grafana dashboards under grafana/ — a Prometheus datasource and dashboard provider under grafana/provisioning/, an operational dashboard (veltrixdb_dashboard.json: throughput, latency percentiles, cache hit rate and evictions, per-disk VLog size and garbage ratio, GC), a larger veltrixdb_complete_dashboard.json, and a CI dashboard (ci_dashboard.json). The runtime, process, system and disk panels query veltrixdb_runtime_* / veltrixdb_process_* / veltrixdb_system_* / veltrixdb_disk_* series from the hardware collector, which v1.16 and earlier do not register, so they stay empty against those servers (New on main after v1.16 — ships in the next release registered).
The repository also ships monitoring/servicemonitor.yaml (scrapes port metrics every 15 s on pods labelled app.kubernetes.io/name=veltrixdb in namespace veltrixdb) and monitoring/prometheus-rules.yaml, a PrometheusRule with 10 alerts in v1.16:
| Alert | Fires on |
|---|---|
VeltrixDBWriteThrottleActive | rate(write_admission_throttles_total[2m]) > 10 for 2 m |
VeltrixDBVLogGCStalled | GC skipped as paused while garbage_ratio > 0.5, 5 m |
VeltrixDBVLogReadErrors | any vlog_gc_read_errors_total increase (critical) |
VeltrixDBScrubCorruption | any scrub_corruption_total increase in 15 m (critical) |
VeltrixDBLowCacheHitRate | hit rate < 50% for 10 m |
VeltrixDBHighReadLatency | read P99 > 10 ms for 5 m |
VeltrixDBNodeFailed | failed minus recovered peers > 0 over 10 m (critical) |
VeltrixDBClusterShrank | cluster_nodes_total below its 1-hour max |
VeltrixDBDiskUsageHigh | /mnt/nvme* > 85% full — needs node-exporter |
VeltrixDBProcessRestarted | process_start_time_seconds{job="veltrixdb"} — never fires against a v1.16 or earlier server, which exports no process_* metrics; supply a process exporter or drop it |
New on main after v1.16 — ships in the next release the rules file has 24 rules (the Helm chart's prometheusRule.extendedAlerts groups match), and a test starts a raft and a replicated server and fails if a rule names a metric neither serves:
- storage: the eight above.
VeltrixDBVLogGCStallednow compares the target's worst disk (on v1.16 it joined a per-node counter with a per-disk ratio and could not fire). - process: disk over 85% full (node-exporter);
VeltrixDBProcessRestartedfires whileprocess_start_time_secondsis less than 5 minutes old (also on a first start). - search: rebuild stuck (not ready, no keys loaded for 30 minutes), partial results,
rebuilding/incompleteerrors for 10 minutes, P99 over 1 s. - raft: snapshot captures failing, snapshot transfers failing (leader), snapshot install or restore failing (follower), log not compacted (retained log over 4× the threshold for 15 minutes), apply lag over 10,000 entries, term churn, no leader.
- replication: full resync / delete gap (critical: deletes may be missing on a replica),
replication_state.jsonwrite errors, a replica catch-up pending for 30 minutes.
Profiling New in v1.7
Start the server with -pprof-addr=127.0.0.1:6060 to serve CPU, heap, goroutine and execution-trace profiles on a separate listener (never the metrics port):
go tool pprof http://127.0.0.1:6060/debug/pprof/profile?seconds=30 # CPU
go tool pprof http://127.0.0.1:6060/debug/pprof/heap # heap
curl -o trace.out 'http://127.0.0.1:6060/debug/pprof/trace?seconds=5' # execution trace
New in v1.16 /debug/pprof/mutex and /debug/pprof/block stay empty unless you also pass -pprof-mutex-fraction N (sample 1 in N mutex contention events; 5–100 is typical) and -pprof-block-rate NS (record blocking events of at least NS nanoseconds, e.g. 10000). Both default to 0 (off) and are ignored without -pprof-addr.
When the experimental C++ network front-end (-net=cpp|uring|poll) is in use, the server also logs a per-stage latency report at shutdown (stages iter, cb_enter, exec, put_sched, put_exec, wake, defer_sched, defer_exec; log2 µs buckets).