How I Actually Find and Fix Slow PostgreSQL Queries
A practical workflow for slow-query incidents: find the fingerprint, read the plan, prove the bottleneck, and ship the smallest safe fix.
Notes for the problems that show up after launch: bad plans, awkward migrations, index debt, vacuum pressure, replica lag, and the small decisions that make PostgreSQL easier to operate.
A practical workflow for slow-query incidents: find the fingerprint, read the plan, prove the bottleneck, and ship the smallest safe fix.
Two columns in a composite index can go in either order. Which order matters more than people expect, and "index everything both ways" is a worse answer than the right call.
Most application queries only care about a subset of rows — non-deleted, recent, active. A partial index covering just that subset is smaller, faster, and cheaper to maintain.
GIN and GiST cover the cases B-tree cannot. They overlap, they have very different cost profiles, and the docs are vague about which to use. Here is the practical answer.
BRIN indexes are 100x smaller than B-tree on the right data. They are also useless on the wrong data. The deciding factor is whether the table is naturally sorted.
Foreign key constraints check the parent side automatically. The child side is your problem. Forgetting this index is one of the most common silent performance bugs.
Expression indexes are fine until your WHERE clause and your CREATE INDEX disagree about which expression to use. Here is what I learned the hard way.
Partitioning solves specific problems. It also creates new ones. Here's an honest look at range, list, and hash partitioning — including the mistakes that will haunt you.
REINDEX is a tool you should know exists and rarely need to reach for. Here is how to tell when an index is genuinely bloated, and how to rebuild it without taking the table offline.
Both enforce uniqueness. They look identical in `\d`. The differences only show up when you try to alter the table or hit them with a partial uniqueness rule.
Primary key choice is one of those decisions that looks academic until you are migrating six months in. Here is the framework I use, and the cases where each one wins.
Choosing between `timestamp` and `timestamptz` looks like a stylistic preference until daylight saving moves the wrong customer's appointment by an hour.