An index can contain every value a query needs and still require heap access. PostgreSQL must also establish that each candidate tuple is visible to the current MVCC snapshot. Index entries do not carry enough transaction visibility state to answer that check on their own.
The visibility map provides a page-level shortcut. When a heap page is marked all-visible, an index-only scan can trust that every tuple on that page is visible and return indexed values without visiting the heap tuple.
Visibility is tracked per heap page
Each heap relation has a visibility map stored as a separate relation fork. The map records two bits for each heap page: one for the all-visible state and one for the all-frozen state.
The all-visible bit is the part relevant to index-only scans. A set bit means PostgreSQL has established that every tuple on that page is visible to all current and future transactions until the page is modified again. That lets the executor avoid consulting tuple headers in the heap for visibility.
The map is deliberately conservative. A set bit is a positive statement that the page satisfies the condition. An unset bit does not prove that the page contains an invisible tuple; it only means the executor cannot use the map as proof.
This distinction keeps correctness independent of map coverage. Missing all-visible bits cost heap accesses, not query accuracy.
Index-only scans still account for MVCC
Consider a table with an index that carries both a search key and a payload column:
CREATE INDEX orders_account_created_idx
ON orders (account_id, created_at)
INCLUDE (total_cents);A query such as:
SELECT created_at, total_cents
FROM orders
WHERE account_id = 42
ORDER BY created_at DESC
LIMIT 50;has all referenced columns available from the index. That makes an index-only scan physically possible, but it does not make heap access impossible.
For each matching index entry, PostgreSQL can inspect the visibility-map bit for the referenced heap page. If the page is all-visible, the index tuple is enough. If the bit is clear, PostgreSQL visits the heap to perform the normal visibility check.
As a result, Index Only Scan in an execution plan does not imply zero heap reads. EXPLAIN (ANALYZE, BUFFERS) can show Heap Fetches for an index-only scan. A nonzero count commonly reflects candidate tuples whose heap pages lacked usable all-visible state.
Data changes clear the shortcut
Data-modifying operations clear visibility-map state for affected pages. That behavior follows directly from MVCC: after a page changes, the previous page-level claim can no longer stand without another inspection.
A frequently updated table can therefore have an index that covers a query while still producing many heap fetches. The index definition and the page visibility state describe different conditions.
This also explains a common change in execution behavior after bulk writes. The index has not become less capable of supplying columns. The heap pages have lost all-visible status, so the executor must visit them until maintenance can establish page-level visibility again.
Vacuum sets all-visible state
Vacuum examines heap pages and can set visibility-map bits when their contents satisfy the required conditions. Once a page is marked all-visible, later index-only scans can use that fact until another modification clears the bit.
This makes vacuum relevant to read paths beyond reclaiming storage from dead tuples. It maintains metadata that can remove heap visits from eligible index-only scans.
The effect depends on workload shape. Mostly static regions of a table can retain all-visible state for long periods. Hot regions that receive regular inserts, updates, or deletes repeatedly lose it. A single table can therefore contain both regions at once, with index-only scans avoiding heap access for some candidate rows and fetching heap tuples for others.
The all-frozen bit serves a separate maintenance purpose. It records that all tuples on the page are frozen, allowing some future vacuum work to skip that page. All-frozen implies a stronger condition than the all-visible state used for the index-only optimization.
Covering indexes and visibility maps solve separate problems
An INCLUDE column can make more query data available in a B-tree index without adding that column to the index search key. This is useful when a query needs a small payload alongside selective key columns.
The visibility map does not expand what an index contains. It answers a different question: whether PostgreSQL can trust the referenced heap page without reading tuple visibility metadata from that page.
Both conditions matter for a heap-free result. The index must be able to provide the required values, and the corresponding heap page must carry all-visible state. Improving only one side does not guarantee that heap fetches disappear.
This boundary matters when evaluating a larger covering index. Extra payload columns consume index space and add write cost. If the target table is modified so often that relevant heap pages rarely remain all-visible, the expected reduction in heap access can be limited even though the index technically supports an index-only plan.
Measure the executor, not only the plan node
The plan node name describes the access strategy, while runtime counters expose how completely the visibility-map shortcut succeeded.
For an eligible query, a useful inspection is:
EXPLAIN (ANALYZE, BUFFERS)
SELECT created_at, total_cents
FROM orders
WHERE account_id = 42
ORDER BY created_at DESC
LIMIT 50;If PostgreSQL selects an index-only scan, the Heap Fetches value adds context that the node name alone cannot provide. A low count indicates that most referenced pages supplied usable all-visible state during execution. A high count means the executor often had to fall back to heap visibility checks.
That number should be interpreted with the table’s write pattern and vacuum activity rather than treated as a fixed property of the index. The same query and index can produce different heap-fetch behavior as page visibility changes over time.
PostgreSQL’s index-only scan is therefore not solely an index feature. It is a cooperation between index contents, MVCC rules, heap-page state, and vacuum-maintained metadata. The visibility map is the small structure that makes skipping the heap safe, and its page-level state sets the practical boundary between an index-only plan and a truly heap-free execution.