转储(Minor freeze)
当 MemTable 的内存使用达到一定阈值时,就需要将 MemTable 中的数据存储到磁盘上以释放内存空间,我们将这个过程称之为“转储”。在转储之前首先需要保证被转储的 MemTable 不再进行新的数据写入,我们将这个过程称之为“冻结”(Minor Freeze),冻结会阻止当前活跃的 MemTable 再有新的写入,并同时生成新的活跃 MemTable。
在对冻结 MemTable 进行转储时,会扫描冻结 MemTable 中的数据行,并将这些数据行存储到 SSTable 中,当一条数据被多个不同事务反复修改时,可能会有多个不同版本的数据行存储到转储 SSTable 中。
转储发生在 MemTable 的大小满足一定条件时,任何 Partition 副本可以独立决定冻结当前 MemTable,并转储到磁盘上。转储出的 SSTable 只与相同大版本的增量数据做数据归并,不与全局静态数据合并。这样设计是基于增量数据远小于全局数据的考虑,使得转储会比较快。
转储触发
转储有两种触发方式:自动触发与手动触发。
当一个租户的 MemTable 内存使用量达到 memstore_limit_percentage 时,就会自动触发转储。您也通过以下的运维命令手动触发转储。
ALTER SYSTEM MINOR FREEZE [zone] | [server_list] | [tenant_list] | [replica]
tenant_list:
TENANT [=] (tenant_name_list)
tenant_name_list:
tenant_name [, tenant_name ...]
replica:
PARTITION_ID [=] 'partition_idx%partition_count@table_id'
server_list:
SERVER [=] ip_port_list
示例:
集群级别转储
alter system minor freeze;
server 级别转储
alter system minor freeze server='192.168.1001:2882'
租户级别转储
alter system minor freeze tenant='prod_tenant'
replica 级别转储
alter system minor freeze alter partition_id = '8%1@1099511627933'
需要注意的是,尽管允许只针对单个 Partition 手动触发 Minor Freeze,但由于多个不同的 Partition 可能共用相同的内存块,因此对单个 Partition 的 Minor Freeze 可能并不能有效地释放内存,而针对租户的 Minor Freeze 可以有效地释放对应租户 MemTable 的内存。
合并(Major freeze)
合并操作(Major freeze)是将动静态数据做归并,会比较费时。当转储产生的增量数据积累到一定程度时,通过 Major freeze 实现大版本的合并。
转储和合并的最大区别在于,合并是集群上所有的 Partition 在一个统一的快照点和全局静态数据进行合并的行为,是一个全局的操作,最终形成一个全局快照。
转储和合并的对比如下表所示:
转储(Minor freeze) | 合并(Major freeze) |
Partition 或者租户级别,只是 MemTable 的物化。 | 全局级别,产生一个全局快照。 |
每个 OBServer 的每个租户独立决定自己 MemTable 的冻结操作,主备 Partition 不保持一致。 | 全局 Partition 一起做 MemTable 的冻结操作,要求主备 Partition 保持一致,在合并时会对数据进行一致性校验。 |
可能包含多个不同版本的数据行 | 只包含快照点的版本行 |
转储只与相同大版本的 Minor SSTable 合并,产生新的 Minor SSTable,所以只包含增量数据,最终被删除的行需要特殊标记。 | 合并会把当前大版本的 SSTable 和 MemTable 与前一个大版本的全量静态数据进行合并,产生新的全量数据。 |
合并有很多种不同的方式,具体的描述如下。
全量合并
全量合并是 OceanBase 数据库的一种合并算法,和 HBase 与 Rocksdb 的 major compaction 过程是类似的。顾名思义,在全量合并过程中,会把当前的基线数据都读取出来,和增量数据合并后,再写到磁盘上去作为新的基线数据。在这个过程中,会把所有数据都重写一遍。全量合并会极大的耗费磁盘 IO 和空间,如非必要或者 DBA 强制指定,OceanBase 数据库一般不会主动做全量合并。
OceanBase 数据库发起的全量合并一般发生在列类型修改等 DDL 操作之后。DDL 变更是实时生效的,不阻塞读写,也不会影响到多副本间的 Paxos 同步,将对存储数据的变更延后到合并的时候来做,这时就需要将所有数据重写一遍。
增量合并
增量合并是相对于全量合并而言的概念,同样是 OceanBase 数据库的一种合并算法。大多数情况下,当需要进行合并时并不是所有的宏块都需要被修改,当一个宏块没有增量修改时,我们可以直接重用它,而不是重写它,我们将这种方式称之为增量合并。相对于全量合并的把所有的宏块的重写一边而言,增量合并只重写发生了修改的宏块。增量合并极大地减少了合并的工作量,也是 OceanBase 数据库目前默认的合并算法。
更进一步地,对于宏块内部的微块,很多情况下也并不是所有的微块都会被修改。当发现宏块有行被修改过时,在处理每一个微块时,会先判断这个微块是否有行被修改过,如果没有,只需要把这个微块的数据直接拷贝到新的宏块上,这样没被修改过的微块就省去了解析行、选择编码规则、对行进行编码以及计算列 checksum 等操作。微块级增量合并进一步减少了合并的时间。
渐进合并
在执行某些 DDL 操作时,例如执行表的加列、减列、修改压缩算法等操作后,可能需要将数据重写一遍。OceanBase 数据库并不会立即对数据执行重写操作,而是将重写动作延迟到合并时进行。基于增量合并的方式,无法完成对未修改数据的重写,为此 OceanBase 数据库引入了“渐进合并”,即把数据的重写分散到多次合并中去做,在一次合并中只进行部分数据的重写。
通过以下命令可以控制一张表的渐进轮次:
ALTER TABLE [table_name] set progressive_merge_num=?
例如:
ALTER TABLE mytest set progressive_merge_num=60
将表 mytest 的渐进轮次设置为 60,当执行加列或减列操作后的 60 次合并过程中,每一次合并会重写 60 分之一的数据,在 60 轮合并过后,数据就被整体重写了一遍。
当未对表的 progressive_merge_num 进行设置时,其默认值为 0,目前语义为在执行需要重写数据的 DDL 操作之后,做渐进轮次为 100 的渐进合并。当表的 progressive_merge_num 被设置为 1 时,表示强制走全量合并。
轮转合并
一般来说合并会在业务低峰期进行,但并不是所有业务都有业务低峰期。在合并期间,会消耗比较多的 CPU 和 IO,此时如果有大量业务请求,势必会对业务造成影响。为了规避合并对业务的影响。借助 OceanBase 数据库的多副本分布式架构,引入了轮转合并的机制。
一般配置下,OceanBase 数据库会同时有 3 个数据副本,当一个数据副本在进行合并时,可以将这个副本上的查询流量切到其他没在合并的集群上面,这样业务的查询就不受每日合并的影响。等这个副本合并完成后,再将查询流量切回来,继续做其他副本的合并,我们将这一机制称之为轮转合并。
为了避免流量切过去后,cache 较冷造成的 rt 波动,在流量切换之前,OceanBase 数据库还会做 cache 的预热,通过参数可以控制预热的时间。
合并触发
合并触发有三种触发方式:自动触发、定时触发与手动触发。
- 当集群中任一租户的 Minor Freeze 次数超过阈值时,就会自动触发整个集群的合并。
- 也可以通过设置参数来在每天的业务低峰期定时触发合并。
ALTER SYSTEM SET major_freeze_duty_time = '02:00';
- 也通过以下的运维命令手动触发合并。
ALTER SYSTEM MAJOR FREEZE;