作者介绍:飞你莫属
原文链接:openGauss内存引擎WAL实现
WAL(Write Ahead Logging)是业界通用的保证数据durability的方法,它的基本思想是:数据更新(表数据或者索引数据等的更新)只能在这些更新被写入日志文件并且flush之后才能完成。openGauss内存引擎MOT(Memory Optimized Table)也不例外。本文将从以下几个方面分析MOT WAL的实现:
事务记录WAL日志的过程如下:
redo log主要由3部分组成:
对于不同的DDL操作(如CreateTable,DropTable,CreateIndex,DropIndex)或者DML操作(如Insert,update,delete)的操作记录,其格式不尽相同,下面会逐一讲述。
EndBlock包括2种,分别是用于标记当前事务日志记录部分结束的PartialRedoTx记录和用于标记当前事务日志记录全部结束的CommitTx记录。
CreateTable日志记录
DropTable日志记录
CreateIndex日志记录
DropIndex日志记录
Insert日志记录
需要指出的是,insert操作会更新索引,但是MOT中索引是不持久化的,所以也不需要记录关于索引的WAL日志,索引会在recovery过程中重建
Update日志记录
Delete日志记录布局
需要指出的是,insert操作会更新索引,但是MOT中索引是不持久化的,所以也不需要记录关于索引的WAL日志,索引会在recovery过程中重建
PartialRedoTx记录
事务写日志记录的过程中,如果RedoLogBuffer的空间不足以容纳当前DDL操作或者DML操作的日志记录,MOT会在当前RedoLogBuffer的尾部写入一个PartialRedoTx记录,表示当前事务的日志记录还没有写完。PartialRedoTx记录格式如下:
CommitTx记录
CommitTx记录用于标记当前事务日志记录已经全部写完。CommitTx记录格式如下:
一个事务中可能包含多个操作,每个操作都有对应的操作记录,这些操作记录会先保存在RedoLogBuffer中一并提交。RedoLogBuffer实际上是一个字节数组,其格式如下:
每个事务都有自己单独的RedoLogBuffer,且任意时刻每个事务都只有一个可供写入的RedoLogBuffer。在写事务日志记录到RedoLogBuffer的过程中,如果事务的RedoLogBuffer无法容纳当前DDL操作或者DML操作的记录,则会执行以下步骤:
MOT提供了3种RedoLogHandler,分别是:
在该模式下,对接收到的RedoLogBuffer直接提交给XLog做进一步处理。事务所在的线程被阻塞,直到事务日志记录写入XLog为止。
AsynchronousRedoLogHandler由一个RedoLogBuffer Pool和一个包括3个元素的RedoLogBufferArray数组组成。
RedoLogBuffer Pool作为RedoLogBuffer的缓存池,RedoLogHandler中用到的RedoLogBuffer都从RedoLogBuffer Pool中分配,不再使用的RedoLogBuffer也会释放到RedoLogBuffer Pool中。
每个RedoLogBufferArray都是一个关于RedoLogBuffer的数组,其中最多存放1000个RedoLogBuffer。AsynchronousRedoLogHandler轮流使用这3个RedoLogBufferArray来存放接收到的RedoLogBuffer,每个RedoLogBufferArray使用一段时间之后,切换到下一个RedoLogBufferArray,在发生RedoLogBufferArray切换之前,所有接收到的RedoLogBuffer都会被添加到当前的RedoLogBufferArray中。为了指示当前正在使用哪个RedoLogBufferArray,AsynchronousRedoLogHandler特地维护了一个ActiveArrayIdx索引。
步骤如下:
对于AsynchronousRedoLogHandler来说,接收到的RedoLogBuffer都保存在RedoLogBufferArray中,在WALWriter执行的时候,才将RedoLogBufferArray中的所有的RedoLogBuffer写入XLog,写入流程如下:
SegmentedGroupSyncRedoLogHandler按照NUMA node进行分组,绑定在相同NUMA node上的线程共享同一个GroupSyncRedoHandler,接收到的RedoLogBuffer会添加到相应的GroupSyncRedoHandler的CommitGroup中,CommitGroup包含一个RedoLogBuffer指针数组,添加到CommitGroup中的RedoLogBuffer的指针就保存在这个数组中,当一定条件满足时,CommitGroup中所有的RedoLogBuffer会被写入XLog。
一个GroupSyncRedoHandler中可能同时存在0个或者多个CommitGroup,但是任意时刻最多只有一个活跃的(与之对应的是关闭的,表示不再接收更多的RedoLogBuffer)CommitGroup,活跃的CommitGroup表示可以继续向其中添加RedoLogBuffer。
向GroupSyncRedoHandler中添加RedoLogBuffer的流程如下:
XLog是Transaction Log的简称,它接收来自于RedoLogHandler的RedoLogBuffer或者RedoLogBuffer数组。它会先将接收到的RedoLogBuffer或者RedoLogBuffer数组组装成WAL log record,然后将这些WAL log record写入WAL log buffer中,最后再将WAL log buffer中的数据写入WAL log file中。
如果接收的是RedoLogBuffer数组,则会遍历RedoLogBuffer数组中的所有的元素,在每个RedoLogBuffer头部4字节中记录RedoLogBuffer大小,并且将RedoLogBuffer组装成WAL log record中。如果接收到的是一个RedoLogBuffer,则相当于接收到的是一个只包含一个元素的RedoLogBuffer数组,处理过程类似。
WAL log record由以下几部分组成:
逻辑上,WAL log file的大小是16EB(8 Byte地址空间),但是系统中不可能有这么大的文件,因此openGauss会将WAL log file切分成16MB(可配置)的片段,这些片段被称为WAL log segment。
WAL log segment会进一步划分为一系列8KB大小的page,每个page的起始位置保存的都是PageHeader,对于每个WAL log segment中的第一个page,其PageHeader类型是XLogLongPageHeaderData,其它page的PageHeader类型是pageXLogPageHeaderData。每个page中,紧接着PageHeader保存的是一系列的WAL log record。
为了进一步的减少WAL log file的I/O操作,PostgreSQL中引入了WAL log buffer,对产生的WAL log record日志进行缓存,合并I/O操作。
WAL log buffer可以理解为一个环形的共享缓存,在每次写入新的日志记录时:
WAL log buffer中的数据最终必须写入WAL log file中,这会在以下时机发生:
WAL log buffer写入WAL log file之后,可能在操作系统的page cache中缓存,并没有真正写入到文件中,所以还需要有flush机制,openGauss支持同步和异步两种方式:
MemFireDB,带你体验不一样的云端飞翔。