Why pg_stat_ch
PostgreSQL ships withpg_stat_statements, which aggregates query statistics in shared memory. It answers “how does this query perform on average?” but cannot answer:
- When did it get slow? Cumulative counters hide time-series trends. You can’t see a latency spike that started 10 minutes ago.
- What happened during that one slow execution? Averages smooth over outliers. A single 30-second query disappears into a mean of 5ms.
- Which application or user caused the load?
pg_stat_statementsgroups by query text, not by client. - What errors are happening and when? Error tracking is not part of
pg_stat_statements.
Architecture
-
Hooks capture query telemetry in the foreground path. The extension hooks into
ExecutorStart,ExecutorRun,ExecutorFinish,ExecutorEnd,ProcessUtility, andemit_logto collect timing, buffer stats, WAL usage, CPU time, JIT metrics, errors, and client context. - Shared-memory ring buffer receives events with no network I/O on the query path. The buffer uses a multi-producer, single-consumer (MPSC) design with batched writes.
- Background worker drains the ring buffer and inserts events to ClickHouse in batches. It runs on a configurable interval (default 200ms) with automatic retry and exponential backoff.
- ClickHouse materialized views handle all aggregation. Pre-built views provide 5-minute query stats with percentiles, per-application load breakdowns, and error feeds. You can add your own views for custom analytics.
What it captures
Every query execution produces an event with these fields:
See the events schema reference for the full field list with types and tuning guidance.
Supported versions
- PostgreSQL 16, 17, and 18
- ClickHouse (any recent version) or OpenTelemetry-compatible collectors
Next steps
Installation
Build from source and load the extension
Quick start
End-to-end setup in 5 minutes with Docker

