Postgres 源码学习 5—FSM 空闲空间映射

前面提到了 Postgres 中的数据文件是被换分为了多个 page,每个 page 的大小默认是 8 KB。当我们向表中插入数据的时候,就需要从这些 page 中找到一个能够放得下这条数据的 page。

因为数据文件 page 的组织是无序的,元组的插入也是无序的,所以如果依次遍历查找满足条件的 page,可能会非常的低效,Postgres 中使用 FSM(Free Space Map) 来进行查找,加速找到适合插入的 page 的过程。

FSM(Free Space Map),即空闲空间映射,其目的主要是快速定位一个有足够空间容纳插入元组的文件页。

我们需要保证这个映射空间尽可能小,并且辅以一个高效的数据组织方式,这样才能够快速的检索。

在 Postgres 中,一个 page 默认的大小是 8KB,默认情况下一个文件的大小是 1GB,所以能够最多容纳 131072 个 page。

如果我们采用一个 32 位 int 类型来表示一个 page 的空闲空间的话,当然是没问题的。但是如果 page 很多的话,每个 page 都需要 32 位来表示空闲空间的值。

FSM 也是需要物理存储的,为了在搜索的时候,能够更加快速,我们需要保证 FSM 占用的空间尽可能的少,所以在 Postgres 中采用了分类别的方式,将空闲空间的大小以 32 为步长,分为了 256 个区间:
img

这样一个 8KB 的 page 的空闲空间大小,使用一个 uint8 类型就可以表示了,由 4 个字节变成了 1 个字节。

如果 page 大小超过了 8KB,例如是 32KB,那么步长也随之增加:32KB / 256

表示空闲空间的数据信息,存储到了在磁盘文件上,以 _fsm 结尾。文件的数据存储方式和 heap page 类似,也是采用了 page 的方式,page 的内部结构和前面提到的 heap page 类似,也有对应的 page header。
img

存储的内容则比 heap page 简单很多,主要是两个属性:

复制代码
/*
 * Structure of a FSM page. See src/backend/storage/freespace/README for
 * details.
 */
typedef struct
{
    /*
     * fsm_search_avail() tries to spread the load of multiple backends by
     * returning different pages to different backends in a round-robin
     * fashion. fp_next_slot points to the next slot to be returned (assuming
     * there's enough space on it for the request). It's defined as an int,
     * because it's updated without an exclusive lock. uint16 would be more
     * appropriate, but int is more likely to be atomically
     * fetchable/storable.
     */
    int         fp_next_slot;

    /*
     * fp_nodes contains the binary tree, stored in array. The first
     * NonLeafNodesPerPage elements are upper nodes, and the following
     * LeafNodesPerPage elements are leaf nodes. Unused nodes are zero.
     */
    uint8       fp_nodes[FLEXIBLE_ARRAY_MEMBER];
} FSMPageData;
  • fp_next_slot:从堆中搜索的起始位置
  • fp_nodes:空闲空间的数据(uint8 类型)

解决了空闲空间占用的问题,接下来就是空闲空间的数据如何组织的问题。其实这可以理解为是一个从无序的数组中,找到一个大于等于给定值的元素。

所以 Postgres 中使用了堆这个数据结构来存储空闲空间的大小,堆的叶子节点对应的是 page 的空闲大小,堆顶元素是最大的元素,当查找是,从堆顶元素进入,依次和其子节点进行对比,一直到达叶子节点。
img

查找的时候,还需要注意一个问题,那就是如果每次都从堆的顶层节点进入,那么有可能不同的进程找到的 page 是一样的。

为了提升并发性能,让不同的进程尽量找到不同的 page,这样能够避免锁竞争。

所以在查找的时候,记录了一个下次开始查找的下标值,如果该下标处的值不满足条件,则跳转到其右边的那个节点,然后从右边的节点的父节点开始查找,以此类推。
img

还需要注意一个问题,一个 FSMPage 有可能存不下所有的 heap page 的空闲空间大小。

所以实际上在存储的时候,会将空闲空间大小存储到不同的 FSM Page 中,那么不同 fsm page 中的空闲空间数据,又怎么维护成一个堆结构呢?

实际上是使用了多层结构,将不同 fsm page 的数据维护成了多个 level 层级的关系。
img

本文由mdnice多平台发布

相关推荐
星浩AI5 小时前
LCEL:打造可观测、可扩展、可部署的 LangChain 应用
人工智能·后端·python
数据小馒头5 小时前
MySQL并发与锁:从“防止超卖”到排查“死锁”
后端
用户298698530145 小时前
C#: 在Word文档中添加或移除可编辑区域
后端·c#
初次攀爬者5 小时前
RAG核心升级|多LLM模型动态切换方案
人工智能·后端·ai编程
EntyIU6 小时前
自己实现mybatisplus的批量插入
java·后端
用户617433273106 小时前
MySQL 表的类 Git 版本控制
后端
pany6 小时前
程序员近十年新年愿望,都有哪些变化?
前端·后端·程序员
杨宁山6 小时前
Java 解析 CDR 文件并计算图形面积的完整方案(支持 MultipartFile / 网络文件)@杨宁山
后端
朱昆鹏6 小时前
IDEA Claude Code or Codex GUI 插件【开源自荐】
前端·后端·github
HashTang6 小时前
买了专业屏只当普通屏用?解锁 BenQ RD280U 的“隐藏”开发者模式
前端·javascript·后端