Postgres Index Advisor from pg_stat_statements Prompt
Mine pg_stat_statements for your most expensive queries and get a prioritized list of missing indexes to add and redundant indexes to drop — with the write cost of each spelled out.
- Target user
- DBAs and platform engineers doing a periodic index health pass
- Difficulty
- Advanced
- Tools
- Claude, ChatGPT, Cursor
The prompt
You are a senior PostgreSQL DBA who treats indexes as a budget: every index buys read speed and costs write throughput, storage, and VACUUM time. You add the few that pay off and remove the dead weight, and you justify each call with data. I will paste: - Top queries by total_exec_time from pg_stat_statements (query, calls, total_exec_time, mean_exec_time, rows): [PG_STAT_STATEMENTS OUTPUT] - Existing indexes per relevant table (\d <table> or pg_indexes): [INDEX DEFINITIONS] - Index usage stats from pg_stat_user_indexes (indexrelname, idx_scan, idx_tup_read): [INDEX USAGE] - Table sizes and rough write rate if known: [TABLE STATS] Do the following: 1. **Rank the pain** — order queries by total_exec_time (the real aggregate cost), not just mean. Note which are read-heavy and frequent enough to justify new indexes. 2. **Recommend missing indexes** — for each hot query, infer the predicate, join, and ORDER BY columns and propose the index (including column order, partial WHERE clauses, covering INCLUDE columns, or expression indexes for function-wrapped predicates). Explain why that column order matters for this query. 3. **Find redundant / unused indexes** — flag indexes with idx_scan at or near zero, and indexes whose leading columns are a prefix of another index (left-prefix redundancy). Recommend dropping only after a usage caveat. 4. **State the write cost** — for each proposed addition, note that it adds work to every INSERT/UPDATE/DELETE touching that table and to autovacuum. Keep the net set small. Output format: (a) a "create" table [index DDL | which queries it helps | est. benefit | write cost], (b) a "drop candidate" table [index | why | confirm-before-dropping check], (c) the exact CREATE INDEX CONCURRENTLY and DROP INDEX CONCURRENTLY statements. Guardrails: reset pg_stat_statements only with a known baseline window in mind. Test every index on a replica/snapshot and confirm with EXPLAIN (ANALYZE, BUFFERS) before prod. Build with CREATE INDEX CONCURRENTLY; drop with DROP INDEX CONCURRENTLY. Before any DROP, verify idx_scan is zero over a representative period — never drop a constraint-backing or unique index without checking what depends on it.
Why this prompt works
pg_stat_statements is the single best source of truth for where a database actually spends its time, but raw output invites the wrong instinct: indexing by mean time, or adding an index per slow query without counting the cost. This prompt ranks by total_exec_time — frequency times cost — so effort lands on the queries that move the aggregate, and it pairs every proposed index with column-order reasoning rather than a blind single-column add.
The redundancy pass is what makes it a true audit. Left-prefix redundancy and zero-scan indexes are silent taxes on write throughput and autovacuum, and they accumulate because dropping feels risky. By demanding a confirm-before-dropping check (and excluding constraint-backing indexes), the prompt makes removal safe instead of scary.
Crucially it frames indexing as a budget with a write-side cost, so the output is a small, defensible net change — and the guardrails (CONCURRENTLY, replica staging, EXPLAIN verification) keep a human reviewing every statement before it reaches production.
Related prompts
-
Postgres Slow Query EXPLAIN Triage Prompt
Turn a confusing EXPLAIN (ANALYZE, BUFFERS) plan into a ranked, plain-English diagnosis with concrete index/rewrite fixes and a verification step — so you fix the real bottleneck, not a guess.
-
Postgres VACUUM, Bloat & Autovacuum Tuning Prompt
Diagnose table and index bloat, decide between VACUUM, autovacuum tuning, and a rebuild, and produce per-table autovacuum settings — so dead tuples and wraparound risk stop quietly degrading your database.