Metrics

August 14, 2026 ยท View on GitHub

Duckgres exposes Prometheus metrics on :9090/metrics. The port is currently fixed. Some control-plane and per-org metrics are available only in the Kubernetes build.

This document is the reference for request-path metrics. The short catalog in the README links here rather than duplicating boundary and aggregation details.

Naming and labels

Metric names follow duckgres_<domain>_<operation>_<measurement>. Durations end in _seconds, monotonically increasing event counts end in _total, and current state uses a gauge without either suffix.

The request path uses these label terms consistently:

  • org: managed warehouse organization ID. It is empty in standalone mode.
  • protocol: protocol boundary; session-start metrics currently emit postgres.
  • outcome: terminal result at the metric's documented boundary.
  • decision: what happened to the polling request in one admission poll.
  • reason: a bounded, metric-specific cause. It is not a poll count.
  • source: how a worker was obtained.
  • phase: one internal worker-acquisition phase.
  • direction: a bounded byte-flow direction; the metadata proxy emits client_to_upstream and upstream_to_client.

Histograms expose the usual _bucket, _count, and _sum series.

Request path boundaries

StageMetricsBoundary
Admission evaluationduckgres_session_admission_evaluation_*One request-owned DB admission poll, including its serialized transaction and lock wait.
Admission queueduckgres_session_admission_wait_seconds, duckgres_session_admission_requests_totalAfter a request is successfully enqueued until grant, hard rejection, timeout, cancellation, or evaluation error. Enqueue failures are excluded.
Admission stateduckgres_session_admission_queue_depth, duckgres_session_admission_active_vcpus, duckgres_session_admission_limit_vcpusLocal waiting callers, live local lease handles, and the effective org cap reconciled for active org stacks from each control-plane process's current config snapshot.
Worker acquisitionduckgres_worker_acquire_*After admission grants until an existing, hot-idle, or newly spawned worker is allocated.
Session startduckgres_session_start_duration_seconds, duckgres_postgres_session_start_totalAfter successful PostgreSQL authentication until ReadyForQuery is flushed or session bootstrap terminates. The counter records exactly one terminal result after server-side retries.
Queryduckgres_query_total, duckgres_query_duration_secondsOne non-empty query attempt and its execution duration.
Native metadata proxyduckgres_metadata_proxy_*The separate metadata SNI branch, from its fail-closed auth/gate through internal Postgres connect and opaque pgwire relay.

Session start includes profile resolution, admission, worker allocation, worker session creation, catalog probing, metadata initialization, session defaults, and the final ready flush. It excludes failed authentication.

Native metadata Postgres proxy metrics

The native metadata proxy branches before worker allocation. It has dedicated metric families rather than adding a protocol label to established metrics:

MetricTypeLabelsMeaning
duckgres_metadata_proxy_connections_openGaugeorgAdmitted proxy connections on this control-plane process, including upstream bootstrap.
duckgres_metadata_proxy_connection_attempts_totalCounterorg, outcomeExactly one terminal event per connection that matched a configured metadata SNI suffix. success is recorded when frontend ReadyForQuery is flushed.
duckgres_metadata_proxy_connection_duration_secondsHistogramorgLifetime from admission against the per-org proxy cap until teardown, including failed upstream bootstrap.
duckgres_metadata_proxy_upstream_connect_duration_secondsHistogramorg, outcomeTime spent connecting and authenticating to the internally resolved metadata Postgres target. Outcomes are success and error.
duckgres_metadata_proxy_bytes_totalCounterorg, directionPost-authentication pgwire bytes relayed in the fixed client_to_upstream or upstream_to_client direction.
duckgres_metadata_proxy_cancel_requests_totalCounteroutcomeRaw proxy CancelRequests handled as session_terminated on the owning replica or not_local when the synthetic key is not registered on this replica.

Connection-attempt outcomes are the closed set success, unavailable, invalid_database, auth_failed, draining, capacity, target_resolution_error, upstream_connect_error, upstream_sync_error, upstream_hijack_error, cancel_key_error, and handshake_error. Unknown or disabled SNI prefixes use an empty org label so denial metrics do not invent tenant values.

The open gauge is process-local because the configured safety cap is enforced per org on each replica. Use sum by (org) for fleet totals and retain the scrape target's pod label when diagnosing one replica at its cap. Counters and histograms can be summed across replicas normally.

duckgres_connections_open is incremented before the SNI branch and therefore includes metadata proxy sockets. Existing worker-path metrics intentionally do not: duckgres_connection_duration_seconds, duckgres_org_sessions_active, duckgres_org_pg_sessions_accepted_total, admission/worker metrics, query metrics, durable query logs, and query traces remain DuckDB-worker-only. The relay is byte-opaque and cannot safely produce statement-level observations. Use the proxy connection/byte metrics together with CNPG and PgBouncer metrics for this path.

Two process-wide security metrics also span the branch boundary. duckgres_auth_failures_total includes wrong-password metadata-proxy attempts; use duckgres_metadata_proxy_connection_attempts_total{outcome="auth_failed"} for the proxy-specific subset. duckgres_rate_limit_rejects_total is incremented for pre-TLS rate-limit rejections, before SNI is available, so those rejected sockets cannot be attributed to either the metadata endpoint or the DuckDB worker endpoint.

The internal upstream session starts with the fixed application_name=duckgres-metadata-proxy, rather than forwarding the client-controlled application name, so it can be distinguished from DuckDB postgres_scanner sessions in pg_stat_activity. A client with full metadata database access can change its application name later, so this is operational attribution, not an authorization control. Upstream availability does not feed the control-plane health endpoint: one org's shard must not remove healthy control-plane pods from service. Target resolution and upstream connect/auth/synchronization share a 10-second bootstrap deadline; established relay traffic has no inherited bootstrap deadline. An admin warehouse update that includes metadata_proxy_enabled reloads the local snapshot and fans a reload out to peer replicas. Established sessions observe a disabled gate on their next five-second authorization recheck after that snapshot reaches the replica.

Metadata-proxy cancellation intentionally closes the whole session, not only the in-flight statement. On a matching synthetic backend key, the owning control-plane replica closes the exact established frontend and upstream connections. It does not redial the PgBouncer Service because cancellation keys are local to the PgBouncer instance that accepted the session. A raw PostgreSQL CancelRequest uses a new TCP connection and the NLB can route it to a different control-plane replica, matching the existing control-plane locality constraint. Such a high-range synthetic-key miss is absorbed and counted with outcome="not_local"; the owning session remains active.

Admission metrics

MetricTypeLabelsMeaning
duckgres_session_admission_evaluation_duration_secondsHistogramdecision, reasonLatency of one DB-backed admission poll for the polling request. It intentionally has no org label.
duckgres_session_admission_evaluations_totalCounterdecision, reasonAdmission poll volume. This can be much larger than request volume because a queued request is polled repeatedly.
duckgres_session_admission_wait_secondsHistogramorg, outcome, reasonEnd-to-end queue wait for each successfully enqueued request.
duckgres_session_admission_requests_totalCounterorg, outcome, reasonExactly one terminal event per successfully enqueued request.
duckgres_session_admission_queue_depthGaugeorgIn-process callers still waiting after successful durable enqueue. It is not a count of durable queue rows.
duckgres_session_admission_active_vcpusGaugeorgRequested vCPUs held by live local lease handles. It is admitted capacity, not measured CPU usage or the exact durable lease-row total.
duckgres_session_admission_limit_vcpusGaugeorgEffective org cap for an active org stack, reconciled from this process's current config snapshot. 0 means unlimited.
duckgres_session_start_duration_secondsHistogramorg, protocol, outcomeAuthenticated PostgreSQL create-to-ready latency.
duckgres_postgres_session_start_totalCounterorg, outcome, reasonExactly one terminal authenticated PostgreSQL session-start result after server-side retries.

Admission request outcomes are granted, rejected, timeout, canceled, and error. rejected means the requested worker shape can never fit its hard organization or user vCPU ceiling. Session-start outcomes are success, timeout, canceled, capacity, draining, and error.

The PostgreSQL terminal counter collapses those outcomes to success or failure. Success always has reason="none". Failure reasons are capacity, worker, metadata_store, control_plane, client, lifecycle, canceled, transport, and unknown. The first four represent failures an operator can usually alleviate. The remaining reasons let alerts exclude bad client input, planned lifecycle transitions, client disconnects, wire errors, and newly added paths that have not yet been classified. capacity covers runtime worker exhaustion and admission timeouts; requests that exceed a configured hard org or user vCPU limit are reported with reason client.

Evaluation decisions are granted_current, already_granted, rejected, blocked, waiting, inactive, missing, canceled, timeout, and error. Each evaluation describes only the polling request. Evaluation reasons are none, org_vcpu, user_vcpu, org_user_vcpu, user_ineligible, resharding, fifo, and store_error.

A terminal request retains vCPU-cap attribution across every admission poll. No blocking poll produces reason="none". If the request encountered an org cap, a user cap, or both, its terminal reason is org_vcpu, user_vcpu, or org_user_vcpu, respectively, even when it also encountered another reason. Without a vCPU-cap reason, one distinct reason is kept as-is and multiple distinct reasons become mixed. An admission store failure contributes store_error; interruption by the caller is classified as cancellation or timeout instead.

Queue depth and active vCPUs are process-local logical contributions. Use sum by (org) across control-plane replicas. Active vCPUs drop when a live lease handle transfers cleanup ownership to the reclaimer; durable rows still awaiting cleanup are excluded, so database-enforced usage can temporarily be higher. Each replica reconciles the limit when it creates, updates, or removes an active org stack, so use max by (org) to collapse the duplicated replica values. Replicas can briefly differ while a config update propagates. Process exit removes its local gauge series automatically.

Admission cleanup metrics

Cleanup ownership is reserved before durable enqueue and retained through the request and lease lifetime. These process-level metrics intentionally omit org and request labels:

MetricTypeLabelsMeaning
duckgres_session_admission_reclaim_pendingGaugeNoneActivated cleanup work retained or executing.
duckgres_session_admission_reclaim_attempts_totalCounteroutcomeExact cleanup attempts; outcomes are success and error.
duckgres_session_admission_reclaim_reservations_in_useGaugeNoneReservations held before enqueue, while queued, by live leases, or by cleanup-pending work.
duckgres_session_admission_reclaim_reservation_capacityGaugeNoneProcess-local cleanup reservation ceiling.
duckgres_session_admission_reclaim_reservation_rejections_totalCounterreasonReservation failures: full, closed, or duplicate.

Sum the gauges across control-plane replicas for fleet totals. Pair cleanup backlog and error rate with the logical admission gauges when diagnosing a gap between live sessions and database-enforced vCPU usage.

Worker and query metrics

The Kubernetes worker allocator keeps its existing names:

MetricLabelsMeaning
duckgres_worker_acquire_total_secondsorg, source, outcomeEnd-to-end worker allocation latency after admission. Sources are idle_reuse, hot_idle_claim, spawn, or none.
duckgres_worker_acquire_gate_wait_secondsorg, outcomeTime waiting for the per-org FIFO worker-acquire gate.
duckgres_worker_acquire_phase_secondsorg, phase, outcomeIndividual hot_idle_claim, spawn, or activate phase latency.
duckgres_query_totalorg, status, reasonOne event per non-empty query attempt. Valid pairs: success/none; failure/user, failure/canceled, failure/conflict; error/metadata_connection_lost, error/system.
duckgres_query_duration_secondsorgQuery execution latency. Use duckgres_query_total for terminal result counts.

The older fleet-level duckgres_control_plane_worker_* metrics remain useful for process-wide worker counts, spawn time, and the approximate post-admission worker queue. They have no org label and are not admission saturation metrics.

Node-local cache proxy metrics

These worker-local metrics have no tenant labels. Exactly one duckgres_worker_cache_proxy_mode{mode} series is 1.

MetricLabelsMeaning
duckgres_worker_cache_proxy_modemodeCurrent cache mode: cached, bypassed, or disabled; the active mode is 1.
duckgres_worker_cache_proxy_bypass_transitions_totalreasonEntries into bypass mode: startup_unavailable, runtime_unavailable, or upstream_unavailable.
duckgres_worker_cache_proxy_bypassed_operations_totalreasonOperations routed around the node-local cache.
duckgres_worker_cache_proxy_reconnect_attempts_totalNoneHealth checks made by the recovery supervisor.
duckgres_worker_cache_proxy_recoveries_totalNoneSuccessful cache re-enablement events.

Cache proxy request-path metrics

These are emitted by the standalone cache-proxy binary itself (cmd/cache-proxy), on its own HEALTH_ADDR /metrics endpoint โ€” a separate process and port from the control plane's :9090. The duckgres_worker_cache_proxy_* family above is the worker-side client wrapper's view; these are the proxy's own view of the requests it served.

MetricTypeLabelsMeaning
cache_proxy_request_duration_secondsHistogrampath, sourceEnd-to-end duration of a served request. path is block (block-aligned cache path) or forward (uncached forward-proxy path); source is local, peer, or s3 for block, and always origin for forward.
cache_proxy_forward_requests_totalCountermethodRequests handled by the uncached forward-proxy path, by HTTP method.
cache_proxy_inflight_requestsGaugeNoneRequests currently being handled by the proxy's request entry point; the queue-depth signal.
cache_proxy_hits_totalCounterNoneWorker-facing cacheable requests served entirely from data already present on local NVMe. Peer API reads and requests that first fetch any block from a peer are excluded.
cache_proxy_misses_totalCounterNoneWorker-facing cacheable requests that require a peer or origin fill before they can be served.
cache_proxy_bytes_served_totalCountersourceDirectional byte mix by local, peer, or s3. Block mode counts assembled response bytes under the slowest source used; the legacy exact-range path counts the local read or deduplicated fill once, so this is not an exact client-egress counter.
cache_proxy_peer_fetches_totalCounterNoneLogical peer lookups. One lookup can issue multiple physical /cache/has probes.
cache_proxy_peer_hits_totalCounterNoneLogical peer lookups followed by a successful peer body transfer.
cache_proxy_peer_probes_totalCounteroutcomePhysical peer availability probe attempts, classified as hit, miss, timeout, canceled, or error. Both a present entry (200) and an in-flight claim (202) are hit; canceled normally means a losing probe was released after another peer answered and is not itself a failure.

Cache-proxy metrics deliberately have no org label. Use the existing per-org Duckgres query and worker-acquisition metrics for customer-facing rollout guardrails, and use these proxy metrics for cache locality and amplification.

Local cache hit ratio:

sum(rate(cache_proxy_hits_total[5m]))
/
clamp_min(
  sum(rate(cache_proxy_hits_total[5m]))
    + sum(rate(cache_proxy_misses_total[5m])),
  1e-9
)

Average physical peer fanout per logical lookup:

sum(rate(cache_proxy_peer_probes_total[5m]))
/
clamp_min(sum(rate(cache_proxy_peer_fetches_total[5m])), 1e-9)

Peer lookup yield:

sum(rate(cache_proxy_peer_hits_total[5m]))
/
clamp_min(sum(rate(cache_proxy_peer_fetches_total[5m])), 1e-9)

For an org-affinity canary, pair those signals with the existing per-org customer failure and worker-acquisition guardrails:

sum by (org) (
  rate(duckgres_query_total{status="error"}[5m])
)
/
clamp_min(
  sum by (org) (rate(duckgres_query_total[5m])),
  1e-9
)

sum by (org, outcome) (
  rate(duckgres_worker_acquire_total_seconds_count{outcome=~"capacity|error|canceled"}[5m])
)
/
on (org) group_left
clamp_min(
  sum by (org) (rate(duckgres_worker_acquire_total_seconds_count[5m])),
  1e-9
)

PromQL recipes

Admission wait p95 by org and terminal result:

histogram_quantile(
  0.95,
  sum by (org, outcome, le) (
    rate(duckgres_session_admission_wait_seconds_bucket[5m])
  )
)

Terminal requests affected by an org or user vCPU cap:

sum by (org, reason) (
  rate(duckgres_session_admission_requests_total{reason=~"org_vcpu|user_vcpu|org_user_vcpu"}[5m])
)

Current queue depth and admitted vCPUs:

sum by (org) (duckgres_session_admission_queue_depth)
sum by (org) (duckgres_session_admission_active_vcpus)

Live admitted-session utilization for capped orgs (limit=0 is deliberately filtered out):

sum by (org) (duckgres_session_admission_active_vcpus)
  / on (org)
(max by (org) (duckgres_session_admission_limit_vcpus) > 0)

Authenticated session-start p95:

histogram_quantile(
  0.95,
  sum by (org, protocol, outcome, le) (
    rate(duckgres_session_start_duration_seconds_bucket[5m])
  )
)

Current metadata proxy connections by org and control-plane pod:

sum by (org, pod) (duckgres_metadata_proxy_connections_open)

Metadata proxy target-phase failure ratio:

sum by (org) (
  rate(duckgres_metadata_proxy_connection_attempts_total{
    outcome=~"target_resolution_error|upstream_(connect|sync|hijack)_error|cancel_key_error"
  }[5m])
)
/
clamp_min(
  sum by (org) (
    rate(duckgres_metadata_proxy_connection_attempts_total{
      outcome=~"success|target_resolution_error|upstream_(connect|sync|hijack)_error|cancel_key_error"
    }[5m])
  ),
  0.001
)

Admission metric migration

The pre-canonical admission family is retired. Existing TSDB history remains, but old series stop receiving samples.

Retired metricReplacementCompatibility note
duckgres_org_connection_admission_duration_seconds{outcome}duckgres_session_admission_evaluation_duration_seconds{decision,reason}Same admission-evaluation layer, with the overloaded outcome split into decision and reason.
duckgres_org_connection_admission_attempts_total{outcome}duckgres_session_admission_evaluations_total{decision,reason}Counts admission polls, not logical requests.
duckgres_org_connection_admission_queue_depth histogramduckgres_session_admission_queue_depth{org} gaugeA current per-process count of waiting callers replaces samples of durable queue shape; the values are not numerically equivalent.
duckgres_org_connection_admission_user_queuesNonePer-user queue-head shape is no longer exported.
duckgres_org_connection_admission_user_limit_skips_total`duckgres_session_admission_requests_total{reason=~"user_vcpuorg_user_vcpu"}`
duckgres_org_connection_admission_ineligible_user_skips_totalduckgres_session_admission_requests_total{reason="user_ineligible"}Measures affected logical requests rather than repeated admission polls; use duckgres_session_admission_evaluations_total{decision="blocked",reason="user_ineligible"} for poll-level diagnostics.
duckgres_org_connection_reclaim_pendingduckgres_session_admission_reclaim_pendingSemantics-preserving prefix rename.
duckgres_org_connection_reclaim_attempts_total{outcome}duckgres_session_admission_reclaim_attempts_total{outcome}Semantics-preserving prefix rename.
duckgres_org_connection_reclaim_reservations_in_useduckgres_session_admission_reclaim_reservations_in_useSemantics-preserving prefix rename.
duckgres_org_connection_reclaim_reservation_capacityduckgres_session_admission_reclaim_reservation_capacitySemantics-preserving prefix rename.
duckgres_org_connection_reclaim_reservation_rejections_total{reason}duckgres_session_admission_reclaim_reservation_rejections_total{reason}Semantics-preserving prefix rename.