海山数据库(He3DB)技术解析:海山PG FSM实现机制

背景

随着数据表不断进行插入、更新和删除元组等操作,数据页内必然会存在空页空间。在插入新的元组时,有两种方式可以选择:直接选择新的页来存放、存放到已有页的空闲空间中。

如果采用第一种存放方式,会明显造成空间利用率的浪费;而采用第二种存放方式,可以明显地节约存储成本,但是这种方式会存在一个问题:如何快速知道哪个页有足够的空闲空间可以存放元组数据,如果每次插入都要遍历所有数据页、直到找到有满足条件的数据页,但是这种方式的查询效率非常低。因此,海山PG采用空闲空间映射(FSM)来记录每个页的空闲空间信息。

通过FSM,在插入新的元组时可以快速地定位到足够空间的空闲页面进行存放,或者发现没有满足条件的页面,需要扩展出一个新页。

实现原理

FSM中存储的并不是一个数据页实际的空闲空间,而是用一个字节来表示一个数据页的空闲空间范围。通过这种方式,可以减少FSM的存储空间,加快搜索查询效率。

字节空闲空间范围(字节)
00~31
132~63
2558164~8191

但是采用这种方式存在有一个问题,如果插入一个元组,需要遍历FSM页,直到找到足够空间的页。如果1个relation占用的磁盘空间是1GB,那么就需要16个FSM页,当插入一个元组时,最坏的情况是需要遍历16个FSM页才能找到合适的页,查询效率太低了。

为了解决搜索效率低下的问题,FSM采用Higher-level和Lower-level结合的方式,可以将一个表中所有页的空闲空间管理起来(Lower-level解决在FSM页内如何快速找到满足条件的空闲页,Higher-level解决如何快速定位满足条件的空闲页在哪个FSM页)。

Lower-level

Lower-level是用来解决在一个FSM页内如何快速找到满足条件的空闲页,下面来介绍FSM页的构造及实现的原理。

为了提高查询效率,FSM页内是通过完全二叉树最大值堆的方式来组织MAIN数据页的空闲空间,FSM页的逻辑结构和物理结构为:

在这里插入图片描述

在这里插入图片描述

其中PageHeader是数据页的页头;fp_next_slot是用于确定下次从该页面中搜索可用空间时的位置,这样分散fsm返回的页面,当几个后端进程同时对一个表进行插入操作,可以给不同后端分配不同的页面,从而避免竞争;剩余的空间是通过数组的方式来保存大根堆的结构,因为页面头部占了一些空间,所以这个二叉树不是满二叉树,右边有些叶子节点是缺失的。上面说了我们用一个字节来表示一个页的空闲空间范围,所以一个FSM页可以记录4069个MAIN数据页的空闲空间。

通过这种组织方式,就可以基于根节点快速判断出这个FSM页面内是否有满足条件的数据块。

快速查找满足条件的叶子节点的流程为:
在这里插入图片描述

① 先确定FSM页面中的叶子节点(叶子节点的slot为0或者fp_next_slot值),然后执行步骤2。

② 然后判断这个slot对应的空闲空间是否满足条件,如果不满足条件,则执行步骤3;否则执行步骤4。

③ 根据搜索算法向上遍历树,直到找到一个满足条件的非叶子节点,然后基于这个非叶子节点,向下遍历,找到满足条件的叶子节点,然后执行步骤4。

④ 更新fp_next_slot值为当前叶子节点的slot+1,然后执行步骤5。

⑤ 返回当前叶子节点的slot值。

Higher-level

通过Lower-level,实现了在FSM页内快速找到满足条件的空闲页,但是如果一个表很大,就会有不止一个的FSM页,那么我们就需要在众多的FSM页面内快速定位到哪个FSM页面能够满足条件。FSM Higher-level的页面多叉树结构如下图的映射关系,逻辑上分为了三层,第一层和第二层是辅助层(Level2和Level1),第三层是储存对应MAIN数据块的空闲空间。一个FSM文件如果采用三层树结构,可以记录40693个叶子节点,对应于40693个表块,远大于2^32 (单个表最多只能有2^32-1个表块)。因此,这样的三层树结构足以记录表文件所有文件块的空闲空间。

在这里插入图片描述

由上图可知:

  1. 第1层FSM块的叶子节点记录了第2层所有FSM块的根节点。

  2. 第2层FSM块的叶子节点记录了第3层所有FSM块的根节点。

  3. 第3层FSM块的叶子节点按从左到右的顺序就是对应表块在表文件中的顺序。

  4. FSM文件中第一个文件块(第0号FSM块)中二叉树根节点所存储的是所有表块空闲空间级别的最大值。

为了便于理解,每个FSM数据块都有一个逻辑地址和物理地址,每个逻辑地址都可以转化为物理地址。FSM文件的物理构造和逻辑地址的关系为:

在这里插入图片描述

逻辑地址包括两方面的内容:Level号和在这一层逻辑页号。从上到下分别为第2层,第1层和第0层。第2层只有一个页面,逻辑地址可以表示为<2,0>,最多可以有4069个孩子节点(即Level 1的节点数)。第1层最多可以有4069个页面节点,逻辑地址可以表示为<1,0>…<1, 4068>,这一层的每个节点最多页都有4069个孩子节点(即Level 0的节点数最多是40694069个)。第0层最多有40694069个页面节点,逻辑地址可以表示为<0,0>…<0,4069*4069>。

物理地址就是数据页在FSM文件中的偏移量。

计算FSM数据块的逻辑地址对应的物理地址的总体思路,是找出在这个数据块之前有多少个Level0、Level1和Level2的节点数,并相加,从而得出该数据块对应的物理地址。比如计算FSMAddress(1,1)节点对应的物理块号,第一步计算出前面有多少个底层的节点,这里只需要计算第三层的节点,等于4069 * 1。然后计算上面每层的排在前面的节点数。第二层的节点数为1,第一层的节点数为1。那么前面总共有 4069 + 1 + 1 = 4071 个节点,因为索引是从0开始计算的,所以它的物理块号就是4071。

查询

当需要插入数据时,通过FSM搜索具有足够空间的页面流程如下图所示:

在这里插入图片描述

① 当需要插入新元组时,需要找到一个数据页来存放该元组,如果FSM文件存在的话就进入FSM模块进行查询合适的数据页。

② 基于该元组占用的空间,计算出所需的最小空间范围值。

③ 读取FSM数据的根节点页面(即Level 2数据块),如果该页面的大根堆的根节点的数值小于所需空间,则认为不存在满足条件的Main数据块,并执行步骤5;否则认为存在满足条件的Main数据块,并执行步骤4。

④ 找到满足条件的叶子节点(即Level 1数据块的逻辑地址),再根据Level1的数据块找到满足条件的Level0数据块,最终找到满足条件的Main数据块,将元组插入到该Main数据块中,之后根据情况决定是否更新Level0的数据块,并结束流程。

⑤ 返回给上层没有合适的数据块可以存放元组数据,上层决定是否需要进行批量拓页,如果需要批量拓页,再批量拓完页之后则更新FSM数据(包括Level2、Level1和Level0),并结束流程;否则结束流程。

更新

除了在插入数据时,会存在更新FSM页面的情况外,如果对表文件做了截断等操作时,可能也会更新FSM页面。在更新FSM页面时,首先更新Level0中FSM页内的数据(先更新叶子节点的数据,如果叶子节点的值大于父节点的值,则更新父节点的值,并以此类推,依次向上更新父节点的值,直到更新根节点的值),如果FSM页内根节点的值发生了变化,则更新Level1中对应的FSM数据页,如果Level1中的FSM页内根节点的值发生了变化,则更新Level2中的FSM页面。

重建

FSM没有使用WAL日志,需要依靠一系列自我纠正措施来修复可能的冲突。首先,无论何时在FSM页面上设置值的时候,该页面内部的大根堆的根节点的值都应该大于或者等于刚刚设置的值,否则会有冲突,某个节点的parent的值太小。其次,如果我们在搜索时发现父节点满足条件,但是该节点的子节点都不满足条件,说明该FSM页面出现了冲突,这时就需要重建FSM页面,来修复页面冲突。

作者介绍

时丕显,移动云数据库内核研发工程师,负责云原生数据库海山PostgreSQL版的功能设计与研发。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值