Learn Labs
Storage Internals

TOAST, FSM & Visibility Map

Storing large values and tracking free space

Heap Storage fixes every page at 8KB. That's fine for a numeric or a short text value, but what happens when you insert a 50KB JSON blob into a single column? Postgres has to move it somewhere else — and it needs to track, per page, how much room is left and which pages are safe to skip during a scan. Three mechanisms handle this: TOAST, the Free Space Map, and the Visibility Map.

TOAST

TOAST (The Oversized-Attribute Storage Technique) kicks in automatically for variable-length columns — text, jsonb, bytea, arrays — once a row would exceed roughly 2KB (Postgres targets fitting at least 4 tuples per page). The oversized value is compressed and, if still too big, sliced into chunks stored in a hidden companion table:

Row Insertcolumn value exceeds ~2KB
Compressstill too big for one page
pg_toast_16391sliced into chunk rows
-- every table with a toastable column gets one of these automatically
SELECT reltoastrelid::regclass FROM pg_class WHERE relname = 'accounts';
-- pg_toast.pg_toast_16391

Each column has a storage strategy controlling this behavior:

  • PLAIN — never compressed or moved out-of-line (used for fixed-size types like integer)
  • EXTENDED — compress first, then move out-of-line if still too large (the default for text/jsonb)
  • EXTERNAL — move out-of-line without compressing (faster substring access, larger storage)
  • MAIN — compress, but avoid moving out-of-line unless there's no other choice

Free Space Map (FSM)

Every relation keeps a Free Space Map — a compact side-file recording roughly how much free space each page has. When you INSERT, Postgres consults the FSM to find a page with room instead of scanning the whole table or always appending to the end. It's what makes space freed by VACUUM reusable rather than wasted.

page 0
12% free
page 1
68% free
page 2
41% free
page 3
3% free
page 4
89% free
page 5
24% free

Visibility Map (VM)

The Visibility Map is a bitmap, one bit (actually two) per page, tracking whether a page is:

  • all-visible — every tuple on the page is visible to all current and future transactions, so no MVCC visibility check is needed
  • all-frozen — every tuple's xmin is old enough that it no longer needs to be considered during transaction-ID wraparound freezing
page 0all-visible
page 1all-visible
page 2not visible
page 3all-visible
page 4not visible
page 5not visible

The VM matters for two very different reasons:

Both maps live in small companion files next to the heap (_fsm, _vm) and are updated incrementally as you write and vacuum. Up next: the mechanism that actually decides which tuples are visible in the first place — MVCC.

On this page