Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Why & How: Free Space Map (FSM)

1. 定义

Free Space Map(FSM):关系的独立 fork(<relfilenode>_fsmFSM_FORKNUM),按 堆/索引页 记录「大约还有多少空闲空间」,供插入与扩展决策快速查询。

  • 每数据页对应 1 个 category 字节:空闲量 ≈ cat * (BLCKSZ/256)(向下取整);假定空闲 < BLCKSZ,故 cat ∈ 0…255。
  • 不存精确字节数,以便 map 小、可树形搜索。
  • Heap 与多数索引(hash 除外)有 FSM。

源码:src/backend/storage/freespace/freespace.cfsmpage.cREADME)。


2. 为何需要

heap_insertRelationGetBufferForTuple 需要一块能容纳新 tuple(含对齐/填充)的页。

若无 FSM,只能从 block 0 起试探或扫表,随 nblocks 恶化。FSM 把「是否存在 ≥ X 空闲」变成对 FSM 树的下降搜索;整棵子树不够时,根节点一次即可否定。返回 InvalidBlockNumber 时由调用方 extend 关系。

信息是近似且可能过期的:并发插入、粒度舍入、未及时 RecordPageWithFreeSpace 都会让「FSM 说够、页上不够」出现;API 契约要求调用方能处理该情况。


3. 页内结构:byte 数组上的 max 树

每个 FSM 页把一棵二叉树摊在数组里:叶子存「某一堆页(或下一层 FSM 页)的空闲类别」;非叶 = 两子的 max

        4
     4     2
   3  4  0  2     <- 叶:对应数据页(或下层 FSM)

因页头占用,叶层不是完美 2 的幂:右侧缺若干叶,上层仍保持完全。对外用「slot」抽象,由 fsm_search_avail / fsm_set_avail 隐藏数组下标细节。

搜索(要 cat ≥ X):从根往下,选「子 ≥ X」的分支;两子都可则按策略选一(可偏向某页邻近,或打散负载)。fp_next_slot 等状态用于轮转起点,减少总挤同一叶。

更新:写叶 → 沿父 bubble up 重算 max,直到根或父值不变。

性质:根 < X ⇒ 本 FSM 页覆盖范围内不存在足够空闲。


4. 跨页:FSM 页树

数据页很多时,底层 FSM 页的根再作为上层 FSM 页的叶子,形成多层。freespace.c 负责地址换算与层间遍历;单页内算法在 fsmpage.c

物理上 FSM fork 随关系增长扩展;与 main fork 的 block 编号通过固定扇出关系映射(见 README「Higher-level structure」)。


5. 对外 API 与插入路径

函数作用
GetPageWithFreeSpace(rel, spaceNeeded)fsm_space_needed_to_catfsm_search;命中返回 BlockNumber,否则 InvalidBlockNumber
RecordPageWithFreeSpace(rel, heapBlk, spaceAvail)把该堆页的实测/估计空闲写回 FSM
RecordAndGetPageWithFreeSpace(...)先更新「刚失败的那页」空闲,再搜下一候选(插入重试)
FreeSpaceMapVacuumVACUUM 后批量校正 FSM(与清理路径配合)

典型插入:

RelationGetBufferForTuple
  -> GetPageWithFreeSpace(spaceNeeded)
       命中 -> 锁页、复核空闲
            不够 -> RecordAndGetPageWithFreeSpace / 再试
       未命中 -> RelationAddBlocks 扩展 main fork,初始化新页
  -> 放入 tuple 后视情况 RecordPageWithFreeSpace

VACUUM / page prune 回收空间后应更新 FSM,否则空闲「看不见」,表会不必要地膨胀。

观测:contrib pg_freespacemap


6. 源码入口

  • 设计说明:src/backend/storage/freespace/README
  • 关系级搜索/记录:freespace.c
  • 页内树:fsmpage.c
  • 插入选页:access/heap/hio.cRelationGetBufferForTuple
  • Fork 常量:FSM_FORKNUMrelfilenode.h / smgr)

7. 小结

  1. FSM = 每数据页一字节空闲类别 + 页内/跨页 max 树,加速「找够大的页」。
  2. 粒度与并发使结果不可盲信;选页后必须在堆页上复核。
  3. 扩展与 VACUUM 都要维护 FSM,否则插入只见「假满」。

相关笔记: FSM README · Page Layout · insert

最后更新: 2026-08-03 | 适用版本: PostgreSQL 15.x / 16.x / devel