GaussDB关键技术原理:高弹性之段页式存储

GaussDB关键技术原理:高弹性之段页式存储

段页式存储 根据前文的介绍,hashbucket需要对文件进行分片,如果直接对单文件管理的数据文件进行切片,将会产生表数量*1024个文件,在表数量较多的场景下,产生的小文件数量多,导致文件管理系统的压力较大,因此,引入段页式管理,属于一个库表空间下的所有表共同使用一组段页式文件,防止bucket化拆分后小文件多的问题。

图1是存储引擎架构示意图,从整个数据库的组成架构看,段页式在其中是存储引擎中最底层的一种文件管理方式,和单文件管理相并列,通过一组段页式文件管理所有用户表,减少单文件数量,从而降低文件系统的压力。段页式管理和单文件管理通过smgr层(介质管理器)对上层提供相同的接口,实现和缓冲区的读写交互,上层业务不感知底层存储方式发生的变化。
在这里插入图片描述
为了向SQL层提供一套接口,段页式表引入了逻辑block的概念,逻辑block是连续页号的page,上层逻辑访问段页式表使用的是逻辑block。每个段页式表都有一个对应的SegmentHeader,SegmentHeader维护了段页式表使用的逻辑block到实际的段页式物理block的映射关系。SegmentHeader管理了一个用户表的所有元数据信息,尤为重要,设计如下:

typedef struct SegmentHead {        
uint64 magic;    XLogRecPtr lsn;    
uint32 nblocks;       // block number reported to upper layer    
uint32 total_blocks; // total blocks can be allocated to table (exclude metadata pages)    
uint64 reserved;    BlockNumber level0_slots[bmt_header_level0_slots];    
BlockNumber level1_slots[bmt_header_level1_slots];    
BlockNumber fork_head[segment_max_forknum + 1];} SegmentHead;

其中,比较重要的有:

nblocks指的是上层看到的最大逻辑页号,和页式存储中的文件大小的概念是一样的,smgrnblocks的返回值。total_blocks指的是分配的所有extent,数据块的个数总和,当申请的extent的pages全部用完时,会再申请一个extent。

level0_slots和level1_slots存放的是逻辑block到物理block的映射关系,对于序号靠前的extent,起始位置直接存储在 level0_slots中。

level0_slots存满之后,会使用level1_slots中存放的level0_slots页面记录。fork_head指的是所有fork的segment head,也以数组的形式存储在main fork的segment head中。每个segment head中的 fork_head[0] 一定等于自己。

hashbucket表采用了段页式存储的方式。

在段页式存储管理下,表空间和数据文件采用段(Segment)、区(Extent)以及页(Page/Block)作为逻辑组织,进行存储的分配和管理。

具体来说,一个database在其所存在的每个表空间中,都拥有一组独立的段页式文件,如下图2所示:某个库下的一个表空间的段页式文件由1-5号文件组成。

在这里插入图片描述
由于属于一个表空间下的所有段页式表的数据都会写到这几个段页式文件中,所以这几个文件会膨胀到TB量级,段页式文件仍然沿用GaussDB页式文件的切分方式,按照1GB进行切分,将一个物理文件称为一个slice,使用2.1, 3.1来表示。拥有相同前缀的一组slice组成一个段页式文件管理系统(SegLogicFile)。SQL层将每个SegLogicFile看作一个无限大的物理文件。

每个SegLogicFile称为一个ExtentGroup,文件组织格式如下图3。

在这里插入图片描述
MapHeader:用来存放管理BitMap page的元信息;
BitMapPages:Bitmap页面,用来管理DataPages的extent使用情况,页面中的一个bit位表示代表1个extent,0表示空闲,1表示已使用。每个ExtentGroup中extent大小不一样,所以1个bit位表示的extent大小也不一样;ReversePointerPages:
reversepointer页面,用来管理DataPages的extent的属主信息,即属于哪个SegmentHeader管理。

DataPages:数据表记录存放区域,不同文件按照不同数量的block组成extent。

GaussDB中有fork的概念,用于管理每个表的数据和元数据信息,如表数据存的文件称作MAIN FORK,空闲页面信息称为FSM FORK,visibility map称为VM FORK。这些fork存储在各自的物理文件中,文件用后缀来区分,1_fsm, 1_vm,MAIN FORK不带后缀。所以,对于GaussDB单文件存储来说,只需要知道relfilenode和fork number,就能拼出物理文件名。

为了前向兼容,段页式保留了fork的概念,一个fork使用一组segment,依然用“relfilenode + fork number +逻辑页号”来访问数据。把MAIN FORK的SegmentHeader,作为该表的relfilenode,存储在系统表pg_class中;其它fork的SegmentHeader存储在MAIN FORK的SegmentHeader中。数据访问的流程如下图4所示:
在这里插入图片描述
其中,表t1在pg_class中的relfilenode是4157,通过访问段页式1号文件的4157页面可以访问到其SegmentHeader,根据上层传入的逻辑页面号0可以通过Block Map Tree找到物理页面的位置(2号文件的4157页面)。通过fork header中的fsm页面4160可以访问到fsm的segmentheader所在的物理信息(1_fsm文件的4160页面)。

由上可知,段页式表通过唯一的Segment Header进行表示,而段页式表中的数据则是存放在若干个Extent中,每个Extent都是由固定数量的Page组成的。所有Extent由1-5号文件组成段空间,所有表都从该段空间中分配数据。因此表的个数和实际物理文件个数无关。

每个表有一个逻辑segment,该表所有的数据都存在该segment上。每个segment会挂载多个extent,每个extent是一块连续的物理页面。 在表或者分区数量较多的场景下, 段页式存储相比于页式存储生成的文件数量会大大减少,从而降低对文件系统的规格要求,减轻对操作系统IO栈产生的压力,提升数据库的吞吐能力。hashbucket表对数据文件进行切片,如果不采用段页式存储可能会产生大量的小文件,在进行CHECKPOINT这种IO重度操作的时候,需要对大量文件进行sync操作,此时很有可能造成长时间的IO等待,影响数据库的整体性能。

段页式目前支持五种大小的Extent,分别是8K/64K/1M/8M/32M,对应五种不同Extent大小的段页式文件(文件名1/2/3/4/5),称之为段页式文件组,这一组文件中1号文件用来管理段页式表元数据,2到5号文件用来存储用户表数据。

对于一个segment来说,每一次扩展的Extent的大小是固定的。前16个extent大小为64K,第17到第143个extent大小为1MB,依次类推。具体参数如下表1所示。初始时表比较小,分配的Extent粒度较小,当表变得比较大的时候,分配的粒度较大。这样做的好处是可以在空间利用率和分配频率之间做一个很好的平衡。

在这里插入图片描述

posted @ 2025-01-22 10:28  喜酱喜酱  阅读(14)  评论(0)    收藏  举报