上篇文章《数据整理的Compact流程 (一)|OceanBase数据转储合并技术解读(二)》中,有讲解到,在OceanBase数据库中,当MemTable写满时,将其下刷到Mini SSTable的过程包含两个步骤:第一步,是将原本可写的MemTable冻结转换为只读的Frozen MemTable;第二步,是将这个已冻结的只读Frozen MemTable持久化存储到磁盘上,形成Mini SSTable。今天,我们将详细探讨这一后续步骤,也就是"转储"的具体执行流程。
整个转储过程可以分为三个阶段:准备阶段、执行阶段以及收尾阶段。
准备阶段
准备阶段主要包括选择这次转储需要处理的Frozen MemTable、拆分此次转储任务的并行度等。
首先是选取Frozen MemTable。我们知道,写到MemTable上的每条数据都会先持久化到写前日志以防止宕机丢失,而由于写前日志追加写的性质,每条数据会拥有一个递增的日志序号,我们这里称其为log_scn
。基于此,我们可以通过log_scn
的范围来标识一个MemTable或者SSTable。
以下图为例,一共有4个Frozen MemTable,分别包含了log_scn
在(0, 100],(100,500],(500,1500]和(1500,2000]的数据。其中(0,500]的两个Frozen MemTable已经合成了一个Mini SSTable,但因为某些原因暂时没有回收。由于(0,500]的两个Frozen MemTable已经持久化到SSTable,我们认为其对应的日志可以回收,因此会存在一个checkpoint_scn
的日志序号,用来表示log_scn
小于该值的数据都已经持久化到SStable了。
那么此时如果新的转储任务到了准备阶段,将通过checkpoint_scn
把Frozen MemTable分为两部分,其中大于checkpoint_scn
的Frozen MemTable被选取为此次转储所需要处理的MemTable。
当选取了Frozen MemTable后,我们会通过第一个Frozen MemTable来划分并行度以及并行区间。具体来说,首先以Frozen MemTable的数据量估计值与一个参数值的比值来得到并行度。我们希望每个子任务处理一个MemTable中约128MB的数据,因此这个参数值默认是128MB(对于想要自定义的用户来说,可以通过修改表属性TABLET_SIZE
来更改)。然后我们根据并行度,将Frozen MemTable内的rowkey大致均分成几个区间。以下图为例,当并行度为4时,理想情况下Frozen MemTable会被分成4份,每份包含1/4 rowkey范围的数据。
执行阶段
执行阶段主要包括迭代行、整合行以及输出行等步骤。
这里我们以一个简化的示例来展开介绍。我们假设数据行是(rowkey, c1, c2, c3)的四列结构,当前转储需要处理以下两个Frozen MemTable,其中从上到下是按照从旧到新的顺序摆放。在MemTable中,每个rowkey都包含一个或多个节点,每个节点代表了对该rowkey对应数据行的一次操作(insert/delete/update)。
首先我们会为每个MemTable生成一个迭代器,每个迭代器将按照rowkey顺序依次迭代,每次将从n个迭代器中得到n行。在下面的例子中,两个迭代器会分别从两个Frozen MemTable中迭代出两行,其中iter1迭代时从最早的行insert开始,将rowkey_A的两行数据整合(fuse)成了一个新的完整行;而iter2则迭代出了一个部分行(只包含rowkey_A以及更新列c3的值)。
当得到n个迭代器吐出的n行后,我们会对结果进行rowkey的比较,从中得出rowkey最小的行。在上面的例子里,我们会得到具有相同rowkey_A的两行。接着我们会将比较后得到的多个具有相同rowkey的行进行整合(fuse),形成一个或多个数据行。下面的例子中,两个最小rowkey的行被整合成了一行,时间上更新的update c3操作被整合到了输出行中。
当我们得到一个最终的输出行后,首先会将其追加写入微块的缓冲区,当缓冲区达到微块大小后,将进行压缩,压缩后的微块数据将追加写入宏块缓冲区,直到宏块缓冲区写满后触发写盘的操作。这里为了简化,我们没有介绍宏块/微块的一些索引结构。
注:迭代行的过程我们省略了事务提交与否、多版本行的与事务相关的复杂概念,感兴趣的同学可以阅读一篇与此相关的博文来进一步了解。
收尾阶段
收尾阶段主要包括生成Mini SSTable、更新table_store以及回收MemTable。
以下图为例,两个Frozen MemTable经过迭代、整合、输出后得到了两个宏块,并且两个宏块在block file上处于并不连续的位置。我们将基于这两个宏块生成一个Mini SSTable结构,通过SSTable的元数据,我们能够定位到其包含宏块的物理位置(注:实现上OceanBase为微块构造了B树形式的索引结构,SSTable的元数据中只需要记录树根就可以方便地进行微块的定位)。接着由于table_store(这里我们可以理解为一个分区的LSM-Tree结构)的MemTable/SSTable组成发生了变化,我们会新生成一个table_store,其中包含新的Mini SSTable以及旧的Major SSTable。最后我们回收数据已被持久化到SSTable的Frozen MemTable。
这篇博客的内容大多基于ob_tablet_merge_task.cpp源码,欢迎大家前往阅读与学习,也欢迎在评论区讨论任何想法和问题。