AI Model Serving & Inference OptimizationPlaybook3 min readUpdated September 2026

Read the Query Plan Before You Add Another Index

The instinct when a query is slow is to add an index and see if it helps. That works often enough to become a habit, and the habit is how a table ends up with a dozen indexes, several of them redundant, all of them slowing down every write that touches it.

The better starting point is the query plan itself. It tells you exactly what the database is doing and exactly where an index would change that, instead of leaving you guessing.

Read the plan before you guess at an index

Running EXPLAIN ANALYZE against a slow query shows you what the planner actually chose: a sequential scan across a large table, a nested loop that's running far more times than expected, a sort spilling to disk because it didn't fit in memory. Each of those points at a different fix, and only one of them is usually solved by adding an index.

A sequential scan on a large table, filtered on a column with no index, is the classic case an index fixes cleanly. A nested loop running more iterations than the row estimates suggested is often a sign the planner's statistics are stale, which no new index will fix on its own, running ANALYZE on the table first will.

Every index has a write cost

An index makes the query it targets faster to read and makes every INSERT, UPDATE, or DELETE that touches the indexed columns slightly slower, since the database has to maintain that index alongside the table itself. One index is a small cost. Ten indexes on a heavily written table is a real one, and it's easy to arrive there one well-intentioned addition at a time.

Before adding an index to fix a slow read, check how write-heavy the table already is. On a table that's read far more than it's written, the tradeoff usually favors adding the index. On a table under heavy write load, it's worth confirming the read the index would help is actually worth that ongoing write cost.

Composite indexes: column order is the whole game

A composite index on two or more columns only helps a query efficiently when that query filters on a leftmost prefix of the index's column order. An index on (customer_id, created_at) speeds up a query filtering on customer_id alone, and one filtering on both columns together, but does nothing useful for a query filtering on created_at by itself.

Getting the column order wrong is one of the most common indexing mistakes, because the index still exists, still gets maintained on every write, and still doesn't help the query it was meant for. Match the column order to how the query actually filters, most selective and most commonly used column first, and check the plan afterward to confirm the index is actually being used the way you intended.

When the fix is a rewrite, not an index

No index fixes a query that's structurally doing too much work. An N+1 pattern, one query per row from an earlier result set, stays slow no matter how well-indexed each individual query is, because the fix is combining them into one, not speeding up the many. A correlated subquery re-executed for every outer row has the same problem: the shape of the query is the bottleneck, not a missing index.

If a query plan shows reasonable index usage on every step and it's still slow, look at how many times each step is actually running before reaching for another index. Sometimes the honest answer is that the query is asking for more work than it needs to.

Finding indexes nobody uses anymore

Indexes accumulate and rarely get removed, since nobody wants to be the one who drops an index and breaks a query they didn't know depended on it. Postgres tracks index usage statistics, and checking them periodically, rather than never, is how you find the ones that are costing writes without paying for themselves on any read.

An automated index advisor can surface candidates worth reviewing on both sides of this, indexes that would help a slow query and indexes that appear unused, but treat its suggestions as a starting point for review, not something to apply directly to a production table. It can't see every query pattern your application runs, only the ones it was told to analyze.

Work through a slow query in this order:

  1. Run EXPLAIN ANALYZE and read what the planner actually chose, such as a sequential scan, an over-repeated nested loop, or a sort spilling to disk.
  2. If row estimates look wrong, run ANALYZE on the table first, because stale statistics cause bad plans that no new index will fix.
  3. Add an index only when the plan shows a scan an index would change, and check the column order of any composite index against the query's filters.
  4. Rewrite the query instead when its shape is the problem, as with an N+1 pattern or a correlated subquery.
  5. Check index usage statistics periodically and drop indexes that cost writes without serving any reads.
Executive Capability Standard

What Good Looks Like

Good indexing means every index on a table is there because a real, measured query plan needed it, not because it seemed like it might help.

Building The Capability (5-Stage Skill Ladder)

1. Learn:Learn to read an EXPLAIN ANALYZE output well enough to tell a sequential scan on a large table from one that's actually fine.
2. Do Manually:Run EXPLAIN ANALYZE on your slowest known queries by hand and note which ones are actually scanning more rows than they need to.
3. Delegate:Have someone own index review for the tables under the heaviest write load, since that's where a wrong index costs the most.
4. Automate:Set up periodic reporting on unused and duplicate indexes so cleanup happens on a schedule instead of never.
5. Buy:An automated index advisor can surface candidates worth reviewing, but treat its suggestions as a starting point, not something to apply unreviewed.

How to Get Started

Frequently Asked Questions

Should I let an automated index advisor apply changes for me?

Treat its suggestions as candidates to review, not changes to apply automatically. An advisor typically can't see your full write pattern or every query your application runs, so a human check before applying anything to a production table is worth the extra step.

What's a covering index?

One that includes every column a query needs, so the database can answer entirely from the index without a separate lookup back to the table. It trades a larger index for fewer reads, worth it for queries run very often.

How do I know if an index is actually being used?

Postgres exposes index usage statistics in its system catalogs. Query them periodically rather than assuming an index is helping just because it exists, since an unused index still costs write performance with no offsetting benefit.

About the numbers

This guide doesn't quote a sourced benchmark. Figures in it are estimates or general guidance, so check them against your own numbers.

Related Guides