Measure PostgreSQL Index Bloat
Use pgstattuple and statistics to estimate index bloat and decide whether a REINDEX is worth the lock cost.
Task
Quantify index bloat with pgstattuple before scheduling a REINDEX.
When to use this
Indexes seem much larger than the data they cover, but no one can say how much space is actually wasted.
Prerequisites
- The pgstattuple extension installed
- Superuser or monitoring role
- A candidate index
Steps
1. Install the extension and sample the index.
CREATE EXTENSION IF NOT EXISTS pgstattuple;
SELECT * FROM pgstatindex('public.idx_events_created');Expected output: The result reports leaf density and free space, giving a bloat estimate.
2. Compare index size with the table it supports.
SELECT pg_size_pretty(pg_relation_size('idx_events_created')) AS index_size,
pg_size_pretty(pg_relation_size('events')) AS table_size;Expected output: An index close to the table size for a low-cardinality predicate signals bloat.
Troubleshooting
Problem
pgstattuple takes a long time
Solution
Use pgstatindex for a faster estimate or run it during low traffic.
Problem
Bloat is real but REINDEX locks
Solution
Consider CONCURRENTLY or partition-wise rebuilds to avoid long locks.
Limitations
- Estimates can be noisy on actively written indexes.
- REINDEX CONCURRENTLY needs extra disk and time.
- Does not cover other bloat sources such as table bloat.
What this recipe does
Quantify index bloat with pgstattuple before scheduling a REINDEX.
Indexes seem much larger than the data they cover, but no one can say how much space is actually wasted.
Prerequisites
- The pgstattuple extension installed
- Superuser or monitoring role
- A candidate index
Procedure
- Install the extension and sample the index.
CREATE EXTENSION IF NOT EXISTS pgstattuple;
SELECT * FROM pgstatindex('public.idx_events_created');
Expected result: The result reports leaf density and free space, giving a bloat estimate.
- Compare index size with the table it supports.
SELECT pg_size_pretty(pg_relation_size('idx_events_created')) AS index_size,
pg_size_pretty(pg_relation_size('events')) AS table_size;
Expected result: An index close to the table size for a low-cardinality predicate signals bloat.
How to confirm it worked
Run the commands above and confirm every expected result in order. If a step does not produce the stated output, use the troubleshooting notes before continuing. A passing run shows the measured bloat ratio justifies a REINDEX and available space covers the concurrent rebuild.
Troubleshooting
pgstattuple takes a long time — Use pgstatindex for a faster estimate or run it during low traffic.
Bloat is real but REINDEX locks — Consider CONCURRENTLY or partition-wise rebuilds to avoid long locks.
Limitations
- Estimates can be noisy on actively written indexes.
- REINDEX CONCURRENTLY needs extra disk and time.
- Does not cover other bloat sources such as table bloat.