Running a database in Kubernetes means more than exposing a port and hoping for the best. Operators need to know: is the process alive? Is it ready to take traffic? Where did this request slow down, and why? ArcadeDB’s Cloud Observability Architecture closes that gap with four independently shippable pillars: Kubernetes health probes, deeper metrics with OTLP export, structured logging with correlation IDs, and distributed tracing via OpenTelemetry.
All of it is opt-in. If you don’t touch a single configuration key, your server behaves exactly as it does today.
The Gap We’re Closing
ArcadeDB already has a solid single-node observability story: Micrometer metrics with an in-memory registry, an optional /prometheus endpoint, JVM and executor-pool binders, a rich engine Profiler (cache hit ratio, pages, WAL, transaction counts, MVCC conflicts), a manual query profiler, and an /api/v1/ready endpoint.
What was missing for cloud-native, Kubernetes-first deployments:
- Metrics depth - query, command, and transaction latency lived only in the manual profiler, with no always-on RED (Rate, Errors, Duration) timers or percentile histograms.
- Distributed tracing - no spans, no trace-context propagation, no way to follow a request across the query engine, a transaction, and a Raft replication hop.
- Structured logging and correlation - logs were text-only, with no JSON output and no way to tie a log line back to the trace or request that produced it.
- Kubernetes health probes - only a readiness endpoint, no standard liveness check.
One Instrumentation Pass, Two Signals
The design leans on a property of Micrometer’s Observation API: instrument a code path once, and it can emit both a metric (a timer) and a span, depending on what’s registered. ArcadeDB already ships Micrometer, so the core server now wraps its hot paths (HTTP request handling, query and command execution, transaction commit, Raft replication) in Observation calls.
With no tracer registered, an Observation is just a metrics-only timer, exactly what happens today. Install the optional tracing plugin, and the same instrumentation points start emitting spans too, with zero additional code paths to maintain.
Pillar 1: Metrics Depth
New always-on RED timers, with percentile histograms and SLO buckets, surfaced in both /prometheus and, optionally, OTLP:
arcadedb.http.requests- tagged by method, path template (never the raw URI, to avoid cardinality blowup), status, and database.arcadedb.query.duration- tagged by language, database, and query type.arcadedb.tx.duration- tagged by database and outcome (commit or rollback).
A new EngineMetricsBinder, modeled on the existing PoolMetrics, bridges the engine Profiler’s cache hit ratio, page and WAL statistics, and MVCC conflict counts, plus database-level and sparse-vector numbers, into Micrometer gauges tagged by database. Set arcadedb.serverMetrics.otlp.enabled=true and these same series also flow to an OTLP collector, no changes to the existing Prometheus scrape path.
Pillar 2: Distributed Tracing
Tracing lives in a brand-new, entirely optional tracing module, mirroring how the existing metrics module is packaged: a separate Maven module, provided scope on the server, loaded via the standard ServerPlugin SPI. The OpenTelemetry SDK, micrometer-tracing-bridge-otel, and the OTLP exporter live only inside this module. The core server’s compile classpath never sees them.
Set arcadedb.serverMetrics.tracing.enabled=true and the plugin registers a bridged tracer into Micrometer’s global ObservationRegistry. The same Observations from Pillar 1 start producing spans, nested correctly from HTTP down through query execution and transaction commit. Inbound HTTP requests continue an upstream trace via the standard W3C traceparent header, and outbound Raft RPCs propagate context so a write traces from leader to follower.
Leave the plugin jar off the classpath, or the flag unset, and it’s a true no-op: zero span overhead, nothing changes.
Pillar 3: Structured Logging and Correlation IDs
Metrics tell you something is slow. Traces tell you where. Logs tell you why. Pillar 3 ties the three together with a correlation ID that flows through all of them.
At the start of each request, ArcadeDB now populates a diagnostic context with the active trace and span IDs (when tracing is on) and a generated request ID (so correlation works even with tracing off), scoped per request and cleared in a finally block to avoid leaking across the server’s worker thread pool.
An opt-in JsonLogFormatter (arcadedb.server.logFormat=json) emits one JSON object per line: timestamp, level, logger, thread, message, trace ID, span ID, database, request ID, and exception. It’s built on ArcadeDB’s existing JSONObject, so there’s no new JSON dependency. The default text formatter is untouched byte-for-byte; if you want trace correlation without switching to JSON, arcadedb.server.logIncludeTrace=true appends a [traceId=...] tag to text-mode log lines instead.
Pillar 4: Kubernetes Health Probes
The smallest, highest-value pillar, and the one that ships first: a new GET /api/v1/health liveness endpoint. It’s cheap by design, no database I/O, no auth required, returning 200 whenever the process and HTTP layer are up. That matters for Kubernetes specifically: liveness must never depend on database readiness, or an orchestrator will kill a node that’s simply still warming up.
The existing /api/v1/ready endpoint is unchanged by default. It gains one optional behavior: set arcadedb.server.readinessRequiresHA=true and, when HA is active, readiness reflects whether the node has joined the Raft group and caught up, giving you a proper readinessProbe for clustered deployments.
livenessProbe:
httpGet:
path: /api/v1/health
port: 2480
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /api/v1/ready
port: 2480
initialDelaySeconds: 5
periodSeconds: 5
Configuration Reference
Every new key defaults to off or to today’s behavior:
| Key | Default | Effect |
|---|---|---|
arcadedb.serverMetrics.otlp.enabled |
false |
Register an OTLP metrics registry alongside Prometheus |
arcadedb.serverMetrics.otlp.endpoint |
http://localhost:4317 |
OTLP metrics endpoint |
arcadedb.serverMetrics.tracing.enabled |
false |
Activate the tracing plugin (no-op if the jar isn’t present) |
arcadedb.serverMetrics.tracing.endpoint |
http://localhost:4317 |
OTLP trace endpoint |
arcadedb.serverMetrics.tracing.samplingRate |
0.0 |
Parent-based sampling ratio |
arcadedb.server.logFormat |
text |
json selects the structured JsonLogFormatter |
arcadedb.server.logIncludeTrace |
false |
Append [traceId=...] to text-mode logs |
arcadedb.server.readinessRequiresHA |
false |
Make /api/v1/ready HA-aware (Raft membership and catch-up) |
Backward Compatibility by Design
Every pillar was built under one non-negotiable constraint: with no new configuration, an upgraded deployment behaves byte-for-byte identically to today. Existing endpoints and response shapes (/prometheus, /api/v1/server, /api/v1/ready) are never modified, only added to. No new mandatory dependencies land on the core server; the OpenTelemetry SDK and its heavier dependencies live exclusively in the optional, SPI-loaded tracing module. No existing configuration key is renamed.
That means you can upgrade today and adopt observability at your own pace, one pillar, one flag, at a time.
Pairing It with Grafana
If you’re already using the ArcadeDB Grafana plugin for BI dashboards over SQL, Cypher, or Gremlin, the new OTLP metrics and traces slot into the same observability stack: point an OpenTelemetry Collector at ArcadeDB, fan out to Prometheus and Tempo (or your vendor of choice), and build Grafana dashboards that go from a metric spike to a trace to the exact log line, without leaving your Kubernetes cluster.