Understand
Mental model
A blocked session is usually a symptom. The useful object is the blocking chain: which backend waits, which backend holds the conflicting lock, how long each transaction has existed, and what business work each session owns. Resolve the root rather than terminating every waiter.
Calibrate
What healthy usually looks like
- Lock waits are brief, attributable, and do not form growing chains.
- Transactions remain short enough that row versions and locks are released predictably.
- Schema changes use lock-aware deployment patterns and explicit timeouts.
Observe
Ask the database a bounded question
Diagnostic query
Find blocked sessions and blocker PIDs
List sessions currently blocked by other backends and show the age of their query and transaction.
SELECT
blocked.pid AS blocked_pid,
pg_blocking_pids(blocked.pid) AS blocking_pids,
blocked.wait_event_type,
blocked.wait_event,
now() - blocked.query_start AS blocked_for,
now() - blocked.xact_start AS transaction_age,
left(blocked.query, 140) AS blocked_query
FROM pg_stat_activity AS blocked
WHERE cardinality(pg_blocking_pids(blocked.pid)) > 0
ORDER BY blocked_for DESC;low observation cost
Interpret
Read the output
- Draw the chain from root blocker to leaf waiters and attach business ownership before terminating anything.
- Distinguish a necessary short lock from an abandoned transaction that has stopped making progress.
- Use `lock_timeout` for lock-sensitive operations; it limits waiting, not statement runtime after the lock is acquired.
Avoid
Common traps
- Killing the longest waiter instead of the root blocker.
- Running a DDL change without understanding the lock mode and queueing effect.
- Treating `idle in transaction` as harmless because no query is currently active.
Verify
Check your reasoning
Continue
Related runbooks
Verify independently