Reading the Postgres planner without flinching
1 min
Most of the fear around query plans comes from reading them top to bottom, like a stack trace. Postgres’s planner doesn’t work that way — it estimates cost bottom-up, from the leaves of the plan tree toward the root, and the first thing worth doing is finding where the estimated row count and the actual row count diverge. That’s almost always where the plan goes wrong.
BUFFERS is the option nobody turns on by default and everybody should. It tells you how much of the work was served from cache versus read from disk, which turns “this query is slow” into “this query is doing 40,000 uncached reads” — a problem you can actually act on.
Three rewrites cover the bulk of what I fix: pushing a filter earlier so the planner doesn’t scan rows it’s going to throw away, replacing an implicit cast that silently disables an index, and rewriting a correlated subquery as a join so the planner can pick a hash strategy instead of nested loops. None of these require exotic knowledge — they require reading the plan slowly enough to notice where the estimate lied.
The goal isn’t to memorize every node type. It’s to build the habit of asking “where did the estimate and reality disagree” every time, and treating the answer as the actual bug report.