Four Postgres Indexing Mistakes I Kept Repeating
Slow queries are rarely a Postgres problem — they're usually an index problem. The four mistakes I made over and over until EXPLAIN ANALYZE became a habit.
Every "Postgres is slow" ticket I've ever investigated turned out to be a missing or misused index. Here are the four mistakes I made repeatedly before I learned to read a query plan.
1. Indexing Columns, Not Queries
I used to add an index to every column that "seemed important." That's backwards. You index for the query patterns your app actually runs.
A composite index on (user_id, status) serves this query in one lookup. Two separate single-column indexes force Postgres to bitmap-and them, which is slower.
2. Getting Composite Column Order Wrong
Order matters in composite indexes. The rule: equality columns first, range columns last.
With (user_id, created_at), Postgres seeks straight to the user's rows then range-scans by date. Reverse it and you scan a date range across all users first.
3. Killing Indexes With Functions
Wrapping an indexed column in a function makes the index useless:
Either normalize on write, or create an expression index that matches the query:
Now the planner can use it. EXPLAIN is the only way to be sure it does.
4. Trusting My Gut Instead of EXPLAIN ANALYZE
This is the meta-mistake. I'd assume an index was being used. EXPLAIN ANALYZE tells you the truth:
Look for Index Scan (good) versus Seq Scan (a full table read). If you see a sequential scan on a big table in a hot query, that's your bug. The actual time numbers also tell you where the real cost is — sometimes it's not where you think.
None of this is advanced. The whole skill is: write the query first, run EXPLAIN ANALYZE, and add the index the plan is begging for. Do that a few times and index design stops being guesswork.