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

相关推荐
郭萌69626 分钟前
Docker 安装陀螺匠教程
后端
u01037310635 分钟前
Django REST Framework (DRF)
后端·python·django
牛马喜喜43 分钟前
sequelize的进阶使用(助力成为优秀全栈)
后端·node.js
勇敢牛牛_1 小时前
【Rust基础】使用Rocket构建基于SSE的流式回复
开发语言·后端·rust
Anarkh_Lee1 小时前
解决 Spring Boot 多数据源环境下事务管理器冲突问题(非Neo4j请求标记了 @Transactional 尝试启动Neo4j的事务管理器)
spring boot·后端·spring
Yharim1 小时前
微服务项目相同nacos地址导致的问题
后端·微服务
SimonKing2 小时前
短信被截断?5分钟用Java打造企业级短链服务
java·后端·架构
Value_Think_Power2 小时前
嵌套go 协程
后端
Cosolar2 小时前
MCP技术应用全景:连接智能世界的万能接口
后端·算法
lzj20143 小时前
芋道源码解读之多租户
后端