Database Performance Monitoring

Database problems are usually slow-developing — the system gets gradually worse over weeks until it tips over and a query that used to be 10ms is 30s. Catching this requires monitoring. The right metrics turn slow degradation into a Tuesday-morning "we should look at that" instead of a 3am page.

This page is the working set of metrics for Postgres specifically; principles transfer.

The signals that matter

Five categories. Get these and you catch most database problems.

CategoryTop metrics
ConnectionsActive + idle counts, max-connection limit utilisation, pool wait time
Queriesp95/p99 latency, slow-query rate, top queries by total time
LocksLock wait time, deadlock count, longest-held locks
I/O & cacheCache hit ratio, dirty page rate, disk I/O wait
Replication & WALReplication lag, WAL volume, archive failures

Each of these has a sane Postgres view to read from. Most observability stacks have prebuilt exporters (postgres_exporter for Prometheus); use them.

Connections

Connection pressure is the single most common Postgres issue. Postgres uses one process per connection; max_connections defaults to 100, can go higher but doesn't scale linearly.

Track:

Alert:

The almost-universal fix for connection pressure is PgBouncer in transaction mode in front of the database. PgBouncer multiplexes thousands of client connections to a pool of hundreds (or fewer) backend connections. Mandatory at any meaningful scale.

Queries: pg_stat_statements

pg_stat_statements is the most useful Postgres extension. It records normalised query stats — total_exec_time, calls, mean_exec_time, rows — for every query the database has seen.

Top-by-total-time view:

SELECT query, calls, total_exec_time, mean_exec_time, rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

This tells you what to optimise. The query at the top of "total time" is where the database is actually spending its life — even if it's individually fast, high call counts add up.

Alert:

Slow query log

Set log_min_duration_statement = 500 (or wherever your latency threshold is). Slow queries land in the Postgres log. Tools like pgBadger summarise.

Use it as the complement to pg_stat_statements — the latter aggregates, the former gives you full bound-parameter examples to reproduce.

Locks and waits

pg_locks joined with pg_stat_activity shows currently-held locks and waiting queries.

Key metrics:

Most problems show up here:

I/O and cache

Postgres uses shared buffers (configurable) plus the OS page cache.

Solutions:

Replication

For setups with replicas:

Alert:

Autovacuum / bloat

Postgres MVCC creates dead tuples; autovacuum cleans them up. When autovacuum can't keep up, tables bloat.

Track:

Tooling: pgstattuple extension shows true bloat. pg_repack rebuilds bloated tables online.

A high-write table with > 50% dead-tuple ratio means autovacuum is losing. Tune autovacuum_vacuum_scale_factor lower for that table; check that long-running transactions aren't blocking vacuum.

What to put on the dashboard

The dashboard a DBA actually looks at:

  1. Connection panel — active, idle, idle in transaction, max.
  2. Query latency panel — p50, p95, p99 over time. Top 10 slow queries by total time.
  3. Lock panel — current waiting queries, longest held lock, recent deadlocks.
  4. Cache panel — hit ratio, top tables by I/O.
  5. Replication panel — replica lag, WAL produced/sec.
  6. Bloat panel — top bloated tables, autovacuum activity.

If you're staring at this for the first time and one of the panels is missing, that's where the next outage is hiding.

Tools

A minimum starter stack

For a team setting up Postgres monitoring from scratch:

- Enable pg_stat_statements
- Enable auto_explain (log_min_duration = 1000, log_analyze = on)
- Run pgBouncer in transaction mode in front of Postgres
- Run postgres_exporter; ship to Prometheus
- Build grafana dashboard with the six panels above
- Set log_min_duration_statement = 500 to catch slow queries
- Configure pgBadger to run nightly on logs
- Set up alerts on the metrics above

A day's work; permanent operational visibility.

Failure modes monitoring catches

Real examples this monitoring stack catches:

Without monitoring, each of these is a frantic investigation. With it, a click and a fix.

Further reading