Why Indexes Speed Reads but Increase Storage and Write Cost
coreintermediateAn index is a separate, ordered structure that lets PostgreSQL find matching rows without scanning the whole table — but that structure is itself real data that must be stored on disk and kept in sync with every INSERT, UPDATE (of an indexed column) and DELETE, so more indexes means faster reads at the direct cost of more storage and slower writes.
Think of it as
An index is not free lookup speed conjured from nothing — it is a second copy of (part of) the data, organized differently, that has to be maintained in lockstep with the table it indexes. Every write that touches an indexed column must also update every index covering that column, which is real, additional work on top of the write to the table itself. This is why "just add an index" is not a universally safe optimization — it is a deliberate trade of write cost and storage for read speed, worth making only when the read benefit outweighs those costs for the actual workload.
What we're doing: Compare INSERT throughput on an unindexed table vs the same table with three indexes, making the write-cost trade-off directly observable.
- 3–5
- The baseline: 100,000 rows into a table with only its implicit primary-key index.
- 7–10
- Three additional indexes, each of which now needs a new entry for every one of those same 100,000 rows.
- 13
- The measured cost difference is the write-amplification effect made concrete — not a hypothetical, but a directly timed comparison.
INSERT 0 100000
Time: 850.331 ms
INSERT 0 100000
Time: 2400.552 msWhy this works: Each of the three extra indexes on the second table needed a new entry for every one of the 100,000 inserted rows — that is 300,000 additional index-entry writes on top of the 100,000 row writes, which is exactly why the timing roughly tripled: the cost is proportional to how many indexes exist, not a fixed overhead.
Adding an index for every column that might ever appear in a WHERE clause "just in case"
Wrong
Better
What you see: A table with many speculative indexes has unexpectedly slow write throughput, and most of the indexes turn out — on inspection with pg_stat_user_indexes — to have near-zero actual scans, meaning their write cost was paid continuously for a read benefit that was never realized.
Why: Every index is a standing cost paid on every relevant write, whether or not it is ever actually used to answer a query — indexing speculatively "just in case" pays that cost in full for a benefit that may never materialize, which is why indexes should be added in response to confirmed query patterns, not anticipated ones.
- No index
- INSERT/UPDATE/DELETE touch only the table
- No extra storage
- Reads must scan every row
- With an index
- Every write also updates the index
- Extra on-disk storage, maintained in lockstep
- Matching reads skip straight to the row
What each index costs, per relevant write
Remember: An index is a real, separate on-disk structure that must be kept in sync with every write — more indexes means faster reads for queries that use them, but more storage and slower INSERT/UPDATE/DELETE for everything else. Add indexes based on confirmed query patterns, not speculative "might need it" coverage.
See also: hot updates · why indexing every column is harmful

