agentsclimarketplace

Free space map

Skill matejformanek/postgres-claude/.claude/skills/free-space-map

Turn Claude Code into a long-term collaborator on PostgreSQL internals — cited knowledge corpus, agent skills, slash commands, and task-shaped scenarios for backend hacking.

Install
npx -y skills add matejformanek/postgres-claude --skill free-space-map

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

PostgreSQL's Free Space Map (FSM) — the tree-of-pages that tracks per-heap-page free space so `INSERT` / `COPY` / `heap_multi_insert` can find a page with room without scanning the whole relation. Covers `src/backend/storage/freespace/` (`freespace.c` + `fsmpage.c` + `indexfsm.c`), the tree layout (leaf-page nodes + 2 upper levels), category encoding (bucketed free-byte counts), the FSM lock discipline, VACUUM's post-scan `FreeSpaceMapVacuum`, and index FSM's simpler use case. Skip when the ask is about visibility map (VM — sibling but different subsystem) or about `pg_freespacemap` contrib module (that's the SQL introspection wrapper).

SKILL.md

10.3 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it

free-space-map — where INSERT finds room

The Free Space Map tracks approximate free space per page in a relation, so INSERT and other appenders can find a target page without scanning. It's a fork of the relation (like the visibility map — a separate file at <relfilenumber>_fsm), organized as a tree over the heap's page numbers.

The design tradeoff: exact byte-level accounting would require WAL-logging every space change and dominate write traffic. Instead, FSM stores a small category per heap page (5 bits, 32 buckets) and updates it lazily — not WAL-logged in the hot path — with periodic reconciliation by VACUUM.

The file map

FileLinesRole
freespace.c~870Main API: RecordAndGetPageWithFreeSpace, GetPageWithFreeSpace, RecordPageWithFreeSpace, FreeSpaceMapVacuum{Range}. Tree traversal + descent.
fsmpage.c~430Per-FSM-page operations. Each FSM page is a binary tree; fsm_search_avail, fsm_set_and_search, path navigation within a page.
indexfsm.c~55The 2-function shim for indexes. Indexes reuse the FSM machinery but only for "is this page reusable?" — the category is 0/nonzero.

The README in src/backend/storage/freespace/ has an ASCII diagram of the tree layout — read it before touching FSM code. It's the shortest path to understanding the layout.

The 3-level tree

FSM's <relfilenumber>_fsm file contains FSM PAGES (not heap pages). Each FSM page holds a binary tree of 4096 slots, packed. Slots at the bottom level correspond to heap pages (one FSM slot ~ one heap page). Slots higher up hold maximums of their children.

Traversal to find a heap page with N bytes free:

  1. Start at the FSM tree root.
  2. At each level: read the root slot's value (which is the max in its subtree). If < N, this subtree can't help.
  3. Otherwise descend into the left or right child (whichever has the max).
  4. Eventually reach a leaf slot → return the heap block number.

Insert of new free-space info:

  1. Update the leaf slot for the heap page.
  2. If the leaf slot changed, propagate up: parent's slot = max(children).
  3. If parent changed, keep propagating.
  4. Stop when a parent's value doesn't change.

The category encoding

Free space is bucketed into 32 categories (5 bits per slot). Category 0 = 0 bytes free. Category 31 = at least 31 × (BLCKSZ / 32) = ~256 bytes buckets. See MaxFSMRequestSize in freespace.h.

Consequence: FSM tells you "at least category N free" but the actual number could be higher. Callers that need EXACT free must double-check the page after locking it. RecordAndGetPageWithFreeSpace also encapsulates the "get + record actual" round-trip.

The lock discipline

FSM pages have their own lock protocol, DIFFERENT from heap pages:

  • RelationExtensionLock (heavyweight lock) — held only while extending a relation (adding a new page). NOT during FSM navigation.
  • FSM page pin + share/exclusive — normal buffer lock semantics, but FSM concurrent readers/writers are very tolerant of stale data because the encoded value is approximate anyway.
  • No WAL logging in the hot pathRecordPageWithFreeSpace is a hint. It's OK if a crash loses the FSM update; VACUUM will reconcile.
  • VACUUM's FSM pass IS loggedFreeSpaceMapVacuum after a heap scan writes canonical values that are WAL'd.

This asymmetry is by design: hot-path INSERTs don't slow down for FSM updates; recovery re-derives from the vacuum-produced records.

The public API surface (freespace.c)

  • GetPageWithFreeSpace(rel, min_space) — read-only: find a page with at least min_space bytes free. Returns block# or InvalidBlockNumber.
  • RecordAndGetPageWithFreeSpace(rel, oldPage, oldFreeSpace, min_space) — combined: record that oldPage has oldFreeSpace bytes free, AND find a new candidate. Common pattern: after failing to fit a tuple, tell FSM the actual remaining free space + get another candidate.
  • RecordPageWithFreeSpace(rel, page, spaceAvail) — pure record; no lookup.
  • XLogRecordPageWithFreeSpace(reln, page, spaceAvail) — write a WAL record about a page's free space (used by heap_multi_insert for post-COPY reconciliation).
  • FreeSpaceMapVacuumRange(rel, start, end) — canonical reconciliation over a page range. Called by VACUUM.
  • FreeSpaceMapVacuum(rel) — same for the whole relation.

When FSM lies (and how)

The category-encoded value can drift below reality:

  • INSERTS that don't fit: caller records the ACTUAL remaining space, so this converges.
  • DELETEs that don't record: FSM stays stale (says "less free" than reality). VACUUM fixes.
  • VACUUM's post-scan pass writes canonical values.

The category-encoded value can also drift ABOVE reality:

  • Concurrent INSERT into the "found" page fills it before you write. Caller must re-check after buffer lock.

This is why RelationGetBufferForTuple (in access/heap/hio.c) has a retry loop — it may consult FSM 2-3 times before either committing to a page or extending the relation.

Index FSM

Indexes track "empty pages that can be recycled" — a bit for each page. Uses the same FSM machinery but only cares about category 0 (fully-empty) vs nonzero. Consumed by btree page-splits (which drop-and-recycle) and by btree page-deletes.

Files: indexfsm.c (the shim), plus per-index-AM users:

  • access/nbtree/nbtxlog.c + nbtree.c — records + reads pages.
  • access/hash/hashovfl.c — hash overflow page recycling (uses its own bitmap for MUCH tighter tracking, not FSM).

Common patch shapes

Add a new FSM consumer

  • Call GetPageWithFreeSpace in the search phase.
  • After committing space use, call RecordPageWithFreeSpace with the remaining.
  • Add a WAL log via XLogRecordPageWithFreeSpace if reliability matters for the recovery-side FSM state.
  • Test with pg_freespacemap (contrib) to verify the FSM state after your workload.

Debug "INSERT is extending the relation instead of reusing space"

  • pg_freespacemap.pg_freespace(rel::regclass, blkno) — see the encoded category.
  • If category=0 for pages that clearly have space: VACUUM hasn't reconciled since the last delete. Run VACUUM.
  • If categories look right but INSERT still extends: your row's min_space request may be larger than any single-page category can encode. Check MaxFSMRequestSize.

Change the category encoding (rare, dangerous)

Would touch fsm_get_max_avail, fsm_free_bits_to_avail, all callers relying on bucket size, plus on-disk file format (bump FSM_PAGE_MAGIC). Almost certainly requires hackers-list discussion.

Pitfalls

  • FSM is NOT crash-safe by default — hot-path updates are unlogged. This is intentional; recovery uses VACUUM's post-scan pass. Don't add "just log it" without hackers discussion.
  • FSM can point at now-full pages — the "found" page may be full by the time you lock it. Every consumer must re-check under buffer lock.
  • Concurrent extensions can race with FSM traversal — a new page appears; FSM tree may not know about it yet. RelationGetBufferForTuple handles this with retry.
  • VACUUM's FSM pass is expensive on huge tables — it walks the whole tree. In PG 17+ it's incremental; in older releases it always did the whole relation.
  • MaxFSMRequestSize — you can only ask for up to this much space; larger requests always miss FSM and extend the relation. Something like ~BLCKSZ/4.
  • Small relations skip FSM — if the relation is under HEAP_FSM_CREATION_THRESHOLD pages, no FSM fork is created (small tables just do sequential-fit). This means the first ~10 pages don't get FSM tracking.
  • Index FSM has different semantics — category 0 vs nonzero, no bucket ladder. Don't code as if index FSM is heap FSM.
  • FSM lives per-fork per-relation — main fork FSM (for INSERT). Toast tables have their own FSM. Indexes have their own FSM. Don't cross-reference.

Related corpus

  • Idiom: no direct FSM idiom (candidate for future authoring). The FSM interacts with heap-tuple-freeze (VACUUM's post-heap-scan FSM pass) and vacuum-two-pass-heap (the orchestration).
  • File docs: knowledge/files/src/backend/storage/freespace/freespace.c.md, fsmpage.c.md, indexfsm.c.md.
  • Subsystems: storage-buffer (FSM pages are buffered), access-heap (hio.c is the primary consumer via RelationGetBufferForTuple), vacuum-autovacuum (the reconciliation driver).
  • README: source/src/backend/storage/freespace/README — the definitive design doc; shorter than this skill and worth reading before deep work.

Corpus-chain shortcut

python3 scripts/corpus-chain.py --file src/backend/storage/freespace/freespace.c
python3 scripts/corpus-chain.py --file src/backend/access/heap/hio.c

Second one shows the primary consumer — RelationGetBufferForTuple + its retry loop.

Boundary

Use this skill for src/backend/storage/freespace/ + FSM consumers.

Don't use for:

  • Visibility Map — sibling machinery in storage/freespace/? NO — VM is in access/heap/visibilitymap.c. Different subsystem, different design.
  • pg_freespacemap contrib — SQL introspection wrapper over FSM; small contrib module, use its own docs if the ask is about the SQL interface.
  • Btree page-deletion tracking — uses index FSM but the recycling logic is in access/nbtree/; skill lives there.
  • Hash overflow-page management — uses its own bitmap, NOT FSM. See contrib-hash or the hash-am subsystem.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 327,069. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.