Skip to content
EL
Contents

Heap access, the visibility map and MVCC storage

Should we index the column we update most?

DecisionAdvanced3 min read10 min hands-on#indexing#storage

The idea in 30 seconds

Indexing a column changes the cost of every update to it, not just of the queries that read it. A column rewritten on every request, like last_seen, should only be indexed if the query needing it is worth turning every one of those writes into a full, index-touching update. Often a coarser column, or a different source for the query, gives the read without the write tax.

Helps to know first: How a table is stored on disk, B-tree indexes

On this page

The situation

Every authenticated request runs UPDATE sessions SET last_seen = now() WHERE id = $1. Product wants an “active in the last 5 minutes” count on the admin dashboard. An AI assistant opened a pull request.

Review taskIndex last_seen for the dashboard

Pull request: “The active-users count scans the whole sessions table. Add an index.”

CREATE INDEX ON sessions (last_seen);

-- dashboard, refreshed every 30 seconds
SELECT count(*) FROM sessions WHERE last_seen > now() - interval '5 minutes';

Would you approve it? Write the review you would leave.

Your call

How to decide

Before indexing a column on an updated table, ask how often that column changes. Indexing a column that changes on most updates turns those updates from heap-only into full updates that touch every index.

Prefer leaving frequently updated columns unindexed, unless the query that needs the index matters more than the write path.

Watch HOT in production: n_tup_hot_upd against n_tup_upd in pg_stat_user_tables. A sudden drop after a migration usually means a new index on an updated column.

Use fillfactor deliberately on tables with frequent updates to non-indexed columns: leaving free space raised HOT from 92% to 100% in our test.

Check yourself

  1. Why does an index on last_seen slow down requests that never read it?

    Show answer

    Every request updates last_seen. Once it is indexed, those updates change an indexed column, so they cannot be HOT and must add entries to every index on the table.

  2. How does a last_seen_minute column keep most updates HOT?

    Show answer

    It changes at most once a minute per session. Updates that only change the unindexed last_seen stay heap-only; only the occasional minute change touches the index.

  3. Which statistic tells you whether a table’s updates are HOT?

    Show answer

    n_tup_hot_upd compared with n_tup_upd in pg_stat_user_tables.

Finished reading?

Saved in this browser only.

Sources

Where each claim on this page comes from.

  1. 1.
    PostgreSQL documentation: 66.7 Heap-Only Tuples (HOT)
    Primary· §66.7 Heap-Only Tuples (HOT)

    An update that changes an indexed column cannot be HOT, so it needs new entries in the table's indexes.

  2. 2.
    PostgreSQL documentation: 27.2 The Cumulative Statistics System
    Primary· §27.2 pg_stat_all_tables

    n_tup_upd and n_tup_hot_upd show how many updates were HOT.

Go further

What to read next, and why it is worth the time.

  1. 1.
    PostgreSQL documentation: 66.7 Heap-Only Tuples (HOT)
    Primary· §66.7

    The two conditions for HOT, which every index decision on an updated table should be checked against.

Applies to PostgreSQL 18. Draft: not yet fact-checked end to end.