Lesson 2 of 12

Heap Pruning and the xmin Horizon: How a Plain SELECT Cleans Up

Applies to PostgreSQL 13–17 Last reviewed Jun 2026 Grounded in source

The one thing to understand first

Most people think only VACUUM removes dead rows. It does not. A plain SELECT can quietly clean up dead row versions on the pages it touches — a mechanism called <a class="sev1-termlink" href="https://thesev1database.com/glossary/heap-pruning/" title="Heap pruning">heap pruning. Whether a row is allowed to be removed comes down to a single moving boundary: the xmin horizon, the oldest transaction snapshot still alive in the system. Understand that boundary and you understand why a forgotten open transaction makes an entire database bloat.

What pruning actually does (and does not do)

The logic lives in src/backend/access/heap/pruneheap.c. When a scan pins a heap page, heap_page_prune_opt() decides whether it is worth cleaning. Pruning is intra-page only: it walks the tuples on that one 8 KB page and reclaims the space of dead versions, but it does not touch indexes — that remains full VACUUM‘s job.

Every tuple on a page is reached through a line pointer (ItemIdData), a tiny slot in the page header. Pruning rewrites those line pointers into one of four states:

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:

  • The cheap early-out: pd_prune_xid
  • HOT chains: pruning’s best case
  • The xmin horizon: how PostgreSQL decides “dead”
  • Layer 3 — Watch it happen on your own database
  • Layer 4 — The levers this hands you
  • Layer 5 — What an Oracle DBA should expect vs what they get
  • All 36 Learning Pathway lessons
  • 170+ cookbook recipes
  • Source-grounded diagnostics & fixes

Secure checkout Cancel anytime Source-grounded

Was this helpful?

← Back to 05 — The Engine Room: How PostgreSQL Actually Executes