Database reliability engineering
Will these action items prevent the next one?
The idea in 30 seconds
After an incident, the action items decide whether it happens again. Items that add capacity, move work to quieter hours or name a person feel decisive and usually change nothing. Good items follow the actual mechanism of the failure: bound the wait that caused it, keep the work that triggered it off the critical path, and make the next one visible within a minute.
Helps to know first: Locks and locking strategies (coming soon), Vacuum, autovacuum and bloat (coming soon), Connection management and pooling (coming soon), Backups, restore and point-in-time recovery (coming soon)
On this page
The situation
Last Tuesday, checkout was down for 14 minutes. Your team lead wrote the postmortem and asks for your review before it is published.
Review taskPostmortem: checkout outage, 14 minutes
Timeline (UTC)
- 13:51 A finance dashboard starts a 25-minute query on
orders, on the primary database. - 14:02 A deploy runs the migration
ALTER TABLE orders ADD COLUMN gift_message text;. - 14:02 Checkout requests start timing out. The migration has not finished.
- 14:04 The database reports
remaining connection slots are reservedand the app returns errors on every page. - 14:06 On-call cannot connect with the app’s credentials; finds an admin password at 14:12.
- 14:16 On-call cancels the dashboard query. The migration completes instantly, and checkout recovers.
Root cause: the migration was too slow for a busy table. The deploying engineer should have checked the table’s size first.
Action items
- Raise
max_connectionsfrom 200 to 1,000 so we never run out of connections again. - Move the database to an instance with twice the CPU.
- Only run migrations between 02:00 and 04:00, when traffic is low.
- The deploying engineer to complete the migrations training.
Would you approve this postmortem? Write the review you would leave.
Your call
Model review
Request changes. The timeline is good; the root cause is wrong, so none of the action items would have prevented this.
The root cause. The migration was not slow. Adding a nullable column without a default is a catalog change that takes about a millisecond; the timeline itself shows it completing “instantly” at 14:16. The outage was a lock queue: the migration waited, with no time limit, for the dashboard’s 25-minute query; every checkout query then queued behind the migration. The real causes are an unbounded lock wait, and a long analytical query running on the primary with no statement limit.
Item 1, more connections: nothing was short of connections. Every one was held by a request waiting in the lock queue. With 1,000 slots, 1,000 requests would have queued instead of 200, each with its own server process, and the outage would have been the same length. Connections ran out as a symptom.
Item 2, more CPU: the database was idle. Every waiting session was waiting on a lock, not computing. Twice the CPU does not make a lock arrive faster.
Item 3, night-time migrations: smaller blast radius, same mechanism. Nightly reports, backups and batch jobs run at exactly those hours, and fewer people are awake to notice. It also makes deploying slower and riskier for everyone, permanently.
Item 4, training: the engineer ran a correct, safe migration. Nothing in the process would have stopped anyone else doing the same. Postmortems are meant to fix systems, not people; an action item naming one person also makes everyone else less willing to write honest postmortems.
What I would ask for instead:
- Bound the wait. Every migration runs with
lock_timeoutof a few seconds, and the migration tool retries with backoff. This alone turns the incident into a failed deploy step. - Bound the dashboard. Give the reporting role a
statement_timeout, and move long analytical queries to a replica. - See it sooner. Alert when any session has waited on a lock for more than 10 seconds, and when the oldest open transaction is older than a few minutes. Both are single queries on
pg_stat_activity. - Keep a way in. Give on-call and monitoring their own role with reserved connection slots, and put the “who is blocking whom” query (
pg_blocking_pids) in the runbook. Six of the 14 minutes were spent getting in. - Rewrite the root cause as the mechanism, without a name in it, so the next reader learns the actual lesson.
Compare it with yours. Did your review catch the main problems?
How to review action items
For each item, ask four questions:
Does it act on the mechanism? Trace the item through the timeline. If the incident would have unfolded the same way with the item in place, it is not a fix, however reasonable it sounds.
Is it a symptom or a cause? Running out of connections, high CPU and slow queries are usually what you saw. Ask what made them happen.
Will it still be true in a year? “Be more careful” and “only at night” decay as the team changes. A default in the migration tool, a role setting or an alert does not.
Does it make the next incident visible sooner? Even a perfect fix leaves other failures. Time to notice and time to get in are often most of the outage, as they were here.
Check yourself
Why would raising
max_connectionsto 1,000 not have shortened this outage?Show answer
Connections ran out because every one was stuck in the lock queue. More slots would have let more requests join the same queue; nothing would have moved until the dashboard query ended.
Which single change would have turned this outage into a failed deploy?
Show answer
Running the migration with a short
lock_timeout. It would have given up after a few seconds, the checkout queries queued behind it would have run, and the deploy could be retried.What evidence would tell you that more CPU was the right fix in a different incident?
Show answer
Sessions in
pg_stat_activitythat areactivewith no wait event, or waiting on CPU-bound work, together with the host’s CPU near its limit. Sessions waiting onLockpoint to contention, not capacity.
Finished reading?
Saved in this browser only.
Sources
Where each claim on this page comes from.
- 1.Site Reliability Engineering, Chapter 15: Postmortem Culture: Learning from FailureReferenceJohn Lunney, Sue Lueder (Google)· Google's Postmortem Philosophy; Best Practice: Avoid Blame and Keep It Constructive
Postmortems should be blameless and assume good intentions; the aim is to fix systems and processes, not people; preventive actions should reduce the likelihood or impact of recurrence.
- 2.PostgreSQL documentation: 1.2 Architectural FundamentalsPrimary· §1.2 Architectural Fundamentals
The PostgreSQL server starts a new process for each client connection.
- 3.PostgreSQL documentation: 9.27 System Information Functions and OperatorsPrimary· §9.27.1 Session Information Functions: pg_blocking_pids
Queued lock requests block later conflicting requests (soft block).
- 4.PostgreSQL documentation: 19.11 Client Connection DefaultsPrimary· §19.11.1 Statement Behavior
lock_timeout and statement_timeout bound how long a statement waits for a lock or runs.
Go further
What to read next, and why it is worth the time.
- 1.Site Reliability Engineering, Chapter 15: Postmortem Culture: Learning from FailureReferenceJohn Lunney, Sue Lueder (Google)
How to run a postmortem that produces changes rather than blame, from a team that has run thousands.
Applies to PostgreSQL 18. Draft: not yet fact-checked end to end.