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多平台发布

相关推荐
写bug写bug1 小时前
11个提高生产力的Go小技巧
后端·go
markix1 小时前
springboot 配置加密,jasypt加解密命令
java·spring boot·后端·jasypt
nbplus_0071 小时前
golang go-bindata打包配置文件嵌入到二进制文件
开发语言·后端·golang·个人开发·go配置文件
努力的小雨2 小时前
解码技术债:AI代码助手与智能体的革新之道
后端·aigc
LiamHong_2 小时前
简单了解 HTTP 请求方法
前端·后端·http·学习方法·web
Lucifer三思而后行2 小时前
Centos 7.9 一键安装 Oracle 12CR2 单机
后端·oracle
Flying_Fish_roe2 小时前
spring-08
java·后端·spring
爱分享的码瑞哥2 小时前
Rust 基础教程
开发语言·后端·rust
辣码甄源2 小时前
“让爷看看”!开源智能坐姿守护者:PoseMonitor
后端·开源·github
LightOfNight3 小时前
【后端面试题】【中间件】【NoSQL】ElasticSearch的优化方案2(减少字段、冷热分离、参数优化)
分布式·后端·elasticsearch·中间件·架构·nosql