Unused Index
System views: pg_stat_user_indexes, pg_indexes, pg_class
Overview
Postgres tracks every time an index is used via pg_stat_user_indexes.idx_scan. An index with zero scans since the last statistics reset is a candidate for removal—it is consuming disk space, slowing every INSERT/UPDATE/DELETE on the table, and increasing checkpoint write pressure without providing any read benefit.
Common causes are: a replaced composite index (both old and new exist), a column migrated out of query patterns, or a premature optimisation index that was never exercised in production.
insightral flags unused indexes on tables with over 1,000 live rows and at least one write since stats reset. Before dropping, confirm stats haven't been reset recently via pg_stat_user_indexes.last_autovacuum.
Detection SQL
Run this query with a read-only role. No writes, no locks.
-- Indexes with zero scans since last stats reset, on tables with recent activity
SELECT
s.schemaname,
s.relname AS table_name,
s.indexrelname AS index_name,
pg_size_pretty(pg_relation_size(s.indexrelid)) AS index_size,
s.idx_scan,
s.idx_tup_read,
s.idx_tup_fetch
FROM pg_stat_user_indexes s
JOIN pg_class c ON c.oid = s.relid
WHERE s.idx_scan = 0
AND c.reltuples > 1000
AND s.schemaname NOT IN ('pg_catalog', 'pg_toast')
ORDER BY pg_relation_size(s.indexrelid) DESC;Fix SQL
Replace placeholders in <angle brackets> with values from the detection output before running.
-- Stage 1: disable the index and monitor for a week before dropping
UPDATE pg_index SET indisvalid = false
WHERE indexrelid = '<index_name>'::regclass;
-- Stage 2: drop once confirmed safe
DROP INDEX CONCURRENTLY <index_name>;Worked example
Table: events (8.4M rows). Index: idx_events_legacy_type (events.event_type), 220 MB.
idx_scan = 0 over 45 days. A newer composite idx_events_type_created covers all queries.
Dropping the legacy index freed 220 MB and reduced INSERT time from 1.8 ms to 1.1 ms at p50.
Common false positives
- Stats were recently reset with SELECT pg_stat_reset() — all idx_scan counts start at zero; check pg_stat_bgwriter.stats_reset timestamp.
- Indexes used only by infrequent batch jobs (e.g., monthly reports) that haven't run yet since the stats reset window.
Check for unused index across your whole estate.
Insightral runs this rule — and 98 more — continuously inside your private cloud, with read-only credentials and nothing leaving your network.
Request early accessLast updated: 2026-05-13 · View all published rules
