Skip to content

Alerting checklist

Six numbers cover nearly every incident in this book. Each query below was proven in a lesson; thresholds are starting points to tune, not laws.

#Alert when…Query coreProven in
1A transaction is older than a few minutesmax(now() - xact_start) from pg_stat_activityLong & idle transactions
2Sessions sit idle in transaction for more than secondscount(*) WHERE state = 'idle in transaction' AND now() - state_change > '30 seconds'Long & idle transactions
3More than a handful of sessions wait on lockscount(*) WHERE wait_event_type = 'Lock'Who is blocking whom
4The deadlock counter jumpsdeadlocks from pg_stat_database (rate)Logs & counters
5Dead tuples dominate a hot table, or autovacuum hasn't touched it latelyn_dead_tup / greatest(n_live_tup, 1), now() - last_autovacuumBloat & vacuum health
6Wraparound margin is half spentage(datfrozenxid) > autovacuum_freeze_max_age / 2Bloat & vacuum health, Wraparound

Three of these alerts are really one story. Alerts 1 through 3 are the same incident caught at different ages: one forgotten transaction becomes a lock queue becomes a full pool, so alert 1 fires first and you treat it as the root rather than three separate pages.

Alert 4 is different in kind — it's a rate, not a level. The deadlock counter never resets on its own, so a steady trickle under load can be normal for your workload while a step change is the thing worth paging on.

Every alert here also has a matching guardrail that prevents the page instead of announcing it: statement_timeout / idle_in_transaction_session_timeout / transaction_timeout, lock_timeout for DDL, and log_lock_waits. An alert that fires often is a setting waiting to be set.

Further reading

MIT Licensed · Every transcript on this site was generated by a real database run against MySQL 8.4.10 and PostgreSQL 18.4 at bd6f201, and re-proven through psycopg and PyMySQL.