Phase 09: Real-World Choke Points & Administration¶
Chapter 35: Monitoring, Observability & Wait Events¶
35.1 pg_stat_activity (The Real-Time Incident Dashboard)¶
During an ongoing outage, this is the first query a senior DBA executes:
SELECT
pid,
now() - pg_stat_activity.query_start AS duration,
usename,
state,
wait_event_type,
wait_event,
query
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY duration DESC;
35.2 Understanding Wait Events (Finding the Real Bottleneck)¶
When queries queue up, the CPU might be low, but the database is unresponsive. The wait_event_type reveals what backend processes are waiting for:
Lock: Waiting on heavyweight table/row lock (e.g., waiting for anALTER TABLEor uncommitted row lock).LWLock(Lightweight Lock): Waiting on shared memory data structures.ProcArrayLock: Too many active concurrent queries competing to check visibility.WALBufMappingLockorWALWriteLock: WAL buffers full; storage cannot write WAL fast enough.IO: Waiting for disk blocks to be read or written.DataFileRead: The requested pages are not inshared_buffersor OS Page Cache; waiting for physical disk read.WALSync: Waiting for disk controller to flush WAL to physical media (fsync).Client:ClientRead: Postgres is waiting for the client application to send a query. If state isidle in transaction, the application opened a transaction and went to sleep!
35.3 Defensive Timeouts (Preventing Runaway Outages)¶
Never run PostgreSQL in production without global safety rails:
* statement_timeout = '30s': Automatically cancels any query running longer than 30 seconds.
* idle_in_transaction_session_timeout = '10s': Kills application sessions that leave a transaction open and idle for \(> 10s\).
* lock_timeout = '3s': Aborts any DDL or lock acquisition if it cannot get the lock in 3 seconds, preventing lock stampedes.
Chapter 36: Post-Mortems: The 4 Common Production Outages¶
36.1 Outage 1: The Linux OOM Killer Strike¶
- Symptom: Postgres suddenly restarts unexpectedly. System log (
dmesg) displays:Out of Memory: Kill process 18452 (postgres) score 852 or sacrifice child. - Root Cause:
work_memwas set too high (e.g., 64MB). A sudden burst of 50 complex analytic queries each ran 4 concurrent hash joins/sorts, attempting to allocate \(50 \times 4 \times 64\text{MB} = 12.8\text{GB}\) of private memory on a 16GB server. - Immediate Fix: Set
vm.overcommit_memory = 2andvm.overcommit_ratio = 80in Linux sysctl to forbid the OS from overpromising RAM. Lower globalwork_memto 4MB–8MB.
36.2 Outage 2: The CPU Cliff from Bad Query Plans¶
- Symptom: CPU utilization jumps to 100%. All application queries experience massive latency spikes.
- Root Cause: A table crossed a threshold where the planner flipped from an Index Scan to a full Sequential Scan due to stale statistics (
pg_statistic), or an application developer deployed an unindexed join. - Immediate Mitigation:
- Find the offending PID in
pg_stat_activity. - Kill the runaway query:
SELECT pg_cancel_backend(pid);(graceful cancel) orSELECT pg_terminate_backend(pid);(force kill). - Run
ANALYZE tablename;to refresh planner statistics.
36.3 Outage 3: Connection Pool Starvation¶
- Symptom: Application logs scream:
FATAL: remaining connection slots are reserved for non-replicated superuser connections. - Root Cause: An external API call inside an application transaction became slow (e.g., waiting 5s for Stripe). The application threads held open DB connections while waiting for HTTP responses, consuming all
max_connections. - Rule: Never make external HTTP/network calls inside a database transaction.
Chapter 37: Upgrading PostgreSQL (Minor vs Major)¶
37.1 Minor Upgrades (e.g., 16.1 -> 16.2)¶
- Binary compatible.
- Replace the PostgreSQL executables and restart the cluster. Downtime = 2 to 5 seconds.
37.2 Major Upgrades (e.g., 14 -> 16)¶
Physical storage formats change across major versions. You cannot just point Postgres 16 at Postgres 14 data files.
Strategy A: pg_upgrade --link (Fastest Physical Upgrade)¶
- Uses hard links (
ln) on the filesystem instead of copying gigabytes of data files. - Upgrades a 5TB database in under 2 minutes!
- Risk: Modifies the original data files in place. If the upgrade fails, you must restore from backup.
Strategy B: Zero-Downtime Logical Replication Upgrade¶
- Spin up a brand-new Postgres 16 cluster alongside the existing Postgres 14 cluster.
- Export schema:
pg_dump --schema-only. - Set up native Logical Replication: PG 14 publishes to PG 16 subscriber.
- Wait for replication lag to reach 0 bytes.
- In a 1-second maintenance window: Switch application connection strings to the new PG 16 cluster. Downtime = Near Zero.