Runbook category
Query performance
Open this category when the ticket says "the database is slow" and the evidence is really one statement: a latency histogram spike, an EXPLAIN that looks wrong, or a single query owning CPU while the rest of the cluster is idle. These runbooks start from the plan rather than from a timer, what the planner chose, what it expected, and what it actually got.
The failure modes here are plan-shaped. A sequential scan that should have been an index, a nested loop on a bad row estimate, a lossy bitmap that rechecks half the table, a CTE that materialised a fence you never asked for, a sort or hash that spilled to disk, parallel workers that never started, or a prepared statement stuck on a generic plan. The fixes range from an index or a statistics object to a work_mem change to leaving the plan alone because the sequential scan was right.
If you do not yet have a plan or a statement id, start in Observability and come back with one. If the plan is fine and the heap is huge with dead tuples, you want Vacuum & bloat. If the symptom is "this session just stopped" with no slow query at the top of pg_stat_statements, you want Locking & concurrency. Indexing is the structural half of many of these fixes; this category is the diagnostic half that tells you whether an index is even the answer.
- Free
Slow query from a sequential scan on a large table
Same query, no code change, quietly slower every month as the table grows.
- Free
Replace deep OFFSET paging with keyset pagination
Page 1 is instant, page 10,000 crawls, and infinite scroll is your slowest endpoint.
- Pro
Tame a lossy bitmap heap scan
Row counts look sane, CPU does not, the plan quietly went lossy on you.
- Pro
Why your query ignores the index, and how to fix it
You built the index. EXPLAIN still says Seq Scan and nobody can say why.
- Pro
Speed up COUNT(*) on a large table
A dashboard polls count(*) and every refresh reads the entire table again.
- Pro
Fix bad row estimates with extended statistics
Estimate says ~1,250 rows, reality is 10,000, and the join strategy goes with the guess.
- Pro
Why parallel query did not kick in
A big aggregate runs on one core while the rest of the box sits idle.
- Pro
Choose the right join: fix a nested loop on a misestimate
A join that returns a single row hashes two whole tables to find it.
- Pro
Control CTE materialization fences
Your WHERE clause runs after the WITH block has already spilled to disk.
- Pro
Refresh stale statistics that break query plans
Plans go wrong straight after a bulk load: estimated 1 row, actual ~100.
- Pro
Replace an N+1 pattern with a LATERAL join
Latest-row-per-parent reads every child row, then throws nearly all of them away.
- Pro
Stop disk-spilled sorts by tuning work_mem
Sort Method says external merge Disk, and no row count explains the extra time.
- Pro
Cure HOT update degradation with fillfactor
Update-heavy table and its indexes keep growing while the row count stays flat.
- Pro
Diagnose detoasting overhead on wide rows
Identical plan, 100x slower the moment you select the big text column.
- Pro
Custom plan versus generic plan in prepared statements
The sixth run of an unchanged prepared statement is twenty times slower than the fifth.
- Pro
Reduce high planning time on complex queries
Execution is milliseconds, the request is not, it is all planning time.
- Pro
Cap parallel workers that multiply CPU and memory
One dashboard query quietly claims a dozen cores because every worker is a whole process.
- Pro
Read the loops multiplier in EXPLAIN ANALYZE
The expensive step in the plan reports a rounding error and the total still makes no sense.