Missing-index advisors lie by omission: they recommend per-query, never see each other's output, and would happily give one table nine overlapping indexes. Feed the raw recommendations plus a workload sample to AI for a consolidation plan — merged keys, order rationale, write-cost warnings — validated with hypothetical indexes before anything is created.
Tier: Core · Workflow area: SQL Authoring & Query Tuning · ~35 minutes
Every engine now emits index advice: Postgres advisors, SQL Server missing-index DMVs, cloud consoles. All of it shares one flaw — recommendations are generated query-by-query with no global view. This playbook uses an LLM for the part advisors skip (cross-query consolidation and write-cost reasoning) and hypopg for the part LLMs cannot do (proof).
ANALYZE first; half the recommendation list may evaporate.Export the raw material: recommendations, existing indexes, write profile.
-- Existing indexes with usage counts (find the dead weight too)
SELECT s.indexrelname, s.idx_scan,
pg_size_pretty(pg_relation_size(s.indexrelid)) AS size,
i.indexdef
FROM pg_stat_user_indexes s
JOIN pg_indexes i ON i.indexname = s.indexrelname
WHERE s.relname = 'orders'
ORDER BY s.idx_scan;
-- Write profile: how expensive is a new index here?
SELECT relname, n_tup_ins, n_tup_upd, n_tup_del, n_tup_hot_upd
FROM pg_stat_user_tables WHERE relname = 'orders';
Add the advisor output itself — pganalyze Index Advisor recommendations, or on SQL Server the missing-index DMV dump (sys.dm_db_missing_index_details joined to _groups and _group_stats).
Ask for consolidation with rationale, not a bigger list.
You are consolidating index recommendations for PostgreSQL 16.
Below: (a) advisor recommendations, (b) existing indexes with scan
counts and sizes, (c) table write rates, (d) the 10 highest-value
queries touching this table.
Produce a consolidation plan:
1. MERGE: recommendations that one index can serve (explain the
key-order choice: equality columns first, then range, then
INCLUDE — justify against the actual queries).
2. DROP candidates: existing indexes that are unused or would be
covered by a merged index.
3. REJECT: recommendations not worth their write cost — show the
write-rate numbers in your reasoning.
4. For every survivor: exact CREATE INDEX DDL, expected serving
queries, and what it costs (size estimate, write amplification).
Flag anything where HOT updates would be broken by indexing a
frequently-updated column.
The HOT-update flag is Postgres-specific gold: indexing a hot-updated column silently converts cheap HOT updates into full index maintenance on every index on the table.
Screen every surviving proposal with hypothetical indexes.
CREATE EXTENSION IF NOT EXISTS hypopg;
SELECT hypopg_create_index(
'CREATE INDEX ON orders (customer_id, status) INCLUDE (ordered_at)');
-- Re-plan each of the queries this index claims to serve:
EXPLAIN SELECT ... ; -- planner must actually choose the hypothetical
SELECT indexname FROM hypopg_list_indexes();
SELECT hypopg_reset();
Record, per proposal, which of its claimed queries actually use it in the plan. An index the planner won't pick is a rejected row, cost-free. A good screen result:
proposal: orders(customer_id, status) INCLUDE (ordered_at)
claimed queries: Q1, Q4, Q7
hypopg result: Q1 ✓ (idx scan, cost 8412 -> 96)
Q4 ✓ (idx-only scan)
Q7 ✗ (planner keeps seq scan — predicate not sargable)
verdict: build; Q7 needs a rewrite, not this index
Validate the drop list just as hard. For each drop candidate, confirm zero scans over a full business cycle (including month-end), then drop reversibly:
-- Postgres has no invisible indexes: snapshot usage, then drop with
-- the recreate statement saved in the ticket.
SELECT indexrelname, idx_scan FROM pg_stat_user_indexes
WHERE relname = 'orders';
DROP INDEX CONCURRENTLY idx_orders_legacy_status; -- recreate DDL in ticket
On MySQL and Oracle, flip the index invisible first and watch for a cycle — the safest drop is one that was invisible for two weeks with no complaints.
Ship builds off-peak, one at a time, and re-measure. CREATE INDEX CONCURRENTLY, then after a business cycle pull pg_stat_user_indexes again: every new index must show scans, every merged-away query must show its improved plan. An unused new index goes back out.
equality_columns, inequality_columns, included_columns and user_seeks/avg_user_impact per recommendation, ask for the same merge/drop/reject split, and validate with a restored copy since hypothetical-index tooling is awkward; check proposed indexes against sys.dm_db_index_usage_stats duplicates first.V$OBJECT_USAGE-style monitoring (ALTER INDEX ... MONITORING USAGE); build survivors as INVISIBLE, enable per-session with OPTIMIZER_USE_INVISIBLE_INDEXES, and flip visible only after plan verification.sys.schema_unused_indexes plus your slow-log digest, and invisible indexes give you both a safe rollout and a safe drop path.Never let an AI-generated DROP INDEX run unreviewed — advisors misjudge, models hallucinate index names, and a dropped unique index is a data-integrity incident, not a performance regression. Human eyes on every statement.
Run this consolidation pass quarterly and diff the index list each time. A table whose index set only ever grows is telling you nobody is doing the reverse pass — and the reverse pass is where the easy wins live.