Repository navigation
Description
Problem
The relay has basic pool utilization gauges (buzz_db_pool_size/idle/active/max) and replica fence metrics, but several critical observability gaps make it difficult to diagnose performance issues:
Missing metrics
-
No query-level latency histograms:
#[datastore_span]creates tracing spans but does not emit Prometheus histograms. Span-based analysis requires a tracing backend (Jaeger/OTEL); the relay's Prometheus metrics endpoint has no per-operation latency data. You cannot answer "what is the p99 latency ofquery_events?" from Prometheus alone. -
No pool acquire wait time: the time between requesting a pool connection and receiving one is invisible. When the pool is saturated, all query latencies spike equally — but the root cause (pool wait vs. query time) is indistinguishable.
-
No advisory lock wait time: advisory lock calls block inside a transaction, but the lock wait is lumped into the enclosing span's duration. There is no way to tell whether a 5-second
replace_addressable_eventwas slow due to query execution or lock contention. -
No transaction duration metric: long-running transactions (replaceable events with large insert + soft-delete + mention insertion) hold connections and potentially block other advisory lock holders. No metric tracks transaction duration.
-
No connection churn metric:
pool_sizeandpool_idleare gauges sampled every 10s. A connection that is acquired and returned 100 times between samples is invisible. A connection-checkout counter would reveal churn rate. -
No per-kind event write latency: all event writes go through
insert_eventorreplace_*_event, but there is no metric broken down by event kind. A single problematic kind (e.g., large NIP-33 parametrized replaceable events) can dominate write latency without being identifiable. -
No
pg_stat_statementsintegration: the relay does not querypg_stat_statements(which is typically enabled on managed Postgres like Aurora). This extension provides per-query mean/max latency, call count, and rows returned — exactly the data needed for slow-query diagnosis. A periodic scrape emitted as Prometheus metrics would close the observability gap.
Missing diagnostic endpoints
-
No
/debug/dbendpoint: there is no admin endpoint that returns current pool state, active queries, advisory lock holders, or connection ages. Diagnosis requires direct Postgres access (pg_stat_activity), which may not be available to all operators. -
No
/debug/locksendpoint: advisory lock contention is the primary deploy-time risk, but there is no way to inspect it withoutpg_locksaccess.
Proposed changes
- Add
buzz_db_operation_duration_secondshistogram with anoperationlabel, emitted from#[datastore_span]. - Add
buzz_db_pool_acquire_wait_secondshistogram (writer and reader pools separately). - Add
buzz_db_advisory_lock_wait_secondshistogram with alock_typelabel. - Add
buzz_db_transaction_duration_secondshistogram. - Add
buzz_db_pool_checkouts_totalcounter. - Add
buzz_db_event_write_duration_secondshistogram with akindlabel for the hot-path event write operations. - Consider periodic
pg_stat_statementsscraping if the extension is available. - Add an admin-only
/debug/dbendpoint behind authentication.
Priority
High — without these metrics, diagnosing deploy-time performance issues requires direct Postgres access and real-time correlation of logs, which is impractical during an incident.
🤖 AI review update (2026-08-23)
Merge the core instrumentation work with #26/#27/#28 under a cardinality budget. Operation, pool_role, and fixed lock_type labels are safe; raw event kind/query ID labels need bounding or coarse families. Prefer external Postgres monitoring or bounded top-N diagnostics for pg_stat_statements, whose identifiers are not stable indefinitely and whose query text can be sensitive. Keep diagnostics off the public relay surface; use a separately authenticated/admin-only mechanism with redaction and query/time limits.
Activity
Add database pressure observability (#6700)
TheSentinel454 commentedon Sep 3, 2026
The core observability program was completed by block#6700 and extended by block#7195: operation, pool-acquire, advisory-lock, transaction, outcome, and active-waiter signals now use bounded vocabularies and have staging/dashboard verification. Optional raw pg_stat/debug endpoint ideas were intentionally not put on the public relay surface.
Add database pressure observability (block#6700)