Cookbook recipe

Index Bloat

Applies to PostgreSQL 13–17 Last reviewed May 2026 Grounded in source
Estimated investigation4 min

Scenario

Scenario A DBA investigates a B-tree index on an events table that has grown to 8 GB while the table itself is only 3 GB. The table experiences high DELETE and UPDATE volume — millions of…

Investigation Path

Scenario

A DBA investigates a B-tree index on an events table that has grown to 8 GB while the table itself is only 3 GB. The table experiences high DELETE and UPDATE volume — millions of event rows are inserted daily and purged after 30 days. Index scans on this table are slower than expected and query plans sometimes fall back to sequential scans.

How to Identify

Conditions:

  • Index size (pg_relation_size on the index) is disproportionate to table size
  • pg_stat_user_indexes shows low idx_scan count despite queries that should use the index
  • EXPLAIN ANALYZE shows slower-than-expected index scans or unexpected Bitmap Heap Scan fallback
  • pgstattuple on the index reveals high dead_tuple_percent
  • No REINDEX has been run since the table had heavy churn

Analysis Steps

-- 1. Compare index size vs table size for all user tables
SELECT
    t.relname                                            AS table_name,
    i.relname                                            AS index_name,
    pg_size_pretty(pg_relation_size(t.oid))              AS table_size,
    pg_size_pretty(pg_relation_size(i.oid))              AS index_size,
    round(pg_relation_size(i.oid)::numeric /
          nullif(pg_relation_size(t.oid), 0), 2)         AS index_to_table_ratio
FROM pg_class t
JOIN pg_index ix  ON t.oid = ix.indrelid
JOIN pg_class i   ON i.oid = ix.indexrelid
WHERE t.relkind = 'r'
  AND t.relnamespace = 'public'::regnamespace
ORDER BY pg_relation_size(i.oid) DESC
LIMIT 20;

-- 2. Check index usage statistics
SELECT indexrelname, idx_scan, idx_tup_read, idx_tup_fetch,
       pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM   pg_stat_user_indexes
WHERE  relname = 'events_bloat'
ORDER  BY pg_relation_size(indexrelid) DESC;

-- 3. Measure actual index bloat with pgstattuple
-- CREATE EXTENSION IF NOT EXISTS pgstattuple;
SELECT * FROM pgstattuple('idx_events_bloat_created_at');

-- 4. Check HOT update ratio to understand if index updates are avoidable
SELECT relname, n_tup_upd, n_tup_hot_upd,
       round(n_tup_hot_upd::numeric / nullif(n_tup_upd, 0) * 100, 1) AS hot_pct
FROM   pg_stat_user_tables
WHERE  relname = 'events_bloat';

-- 5. Verify index scan performance after REINDEX
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM events_bloat WHERE created_at > now() - INTERVAL '1 day';

Pitfalls

  • REINDEX blocks reads AND writes — it takes a ShareLock on the table. Use REINDEX CONCURRENTLY (PostgreSQL 12+) to rebuild without blocking production traffic.
  • Index bloat does not self-heal with regular VACUUMVACUUM marks dead index entries as reusable for future inserts but does not compact the index B-tree structure. Only REINDEX or VACUUM FULL rebuilds the index compactly.
  • REINDEX CONCURRENTLY can fail if the new index build encounters errors — the old index remains valid but a leftover INVALID index may appear in pg_indexes. Check with SELECT * FROM pg_indexes WHERE indexdef IS NULL after the operation.
  • Unused indexes still incur maintenance overhead on every INSERT/UPDATE/DELETE even when they are heavily bloated.

Resolution Approach

Index bloat accumulates when rows are deleted or updated on indexed columns — B-tree pages fill with dead entries that VACUUM cannot compact. The fix path:

This is a Pro lesson

Get every Learning Pathway and cookbook recipe — grounded in PostgreSQL source code, with diagnostics, fixes, and prevention for each topic.

Continue this lesson to learn:

  • Mitigation Actions
  • All 36 Learning Pathway lessons
  • 170+ cookbook recipes
  • Source-grounded diagnostics & fixes

Secure checkout Cancel anytime Source-grounded

Career Impact

This scenario builds production judgment and operational confidence under pressure.

Open Career Dashboard →

Keep going

Related & next steps

Was this helpful?

← All cookbook recipes