OceanBase 数据库的存储引擎基于 LSM-Tree 结构,LSM-Tree 是日志结构的一个归并函数,核心特点是利用顺序写来提高写性能。
它将某个对象(Partition)中的数据按照“key-value”形式在磁盘上有序存储到 SSTable 中。数据更新先记录在 MemStore 中的 MemTable 里,然后再合并(Merge)到底层的 SSTable 里。SSTable 和 MemTable 之间可以有多级中间数据,同样以 key-value 形式保存在磁盘上,逐级向下合并。
OceanBase MemTable 采用双索引结构,即 B+ 树索引和哈希索引。B+ 树索引能够更好地支持范围查找,而哈希索引是针对单行查找的一种优化。每次执行事务时,MemTable 会自动维护 B+ 树索引与哈希索引之间的一致性。如果要读取更老的历史快照,只需要顺着内存中的反向指针往前回溯即可,相当于在内存中执行数据库 Undo 操作。
基于 LSM Tree 的实践:合并
1、合并触发方式
合并的触发方式有三种,分别是定时合并、自动合并和手动合并。
定时合并由 major_freeze_duty_time 参数控制定时合并时间,可以修改参数控制合并时间。
alter system set major_freeze_duty_time='02:00';
当租户的 MemStore 内存使用率达到 freeze_trigger_percentage 参数的值,并且转储的次数已经达到了 minor_freeze_times 参数的值,会自动触发合并。
在" root@sys "用户下,可以通过以下命令发起手动合并(忽略当前 MemStore 的使用率):
alter system major freeze;
合并发起以后,可以通过以下两种命令,在 OceanBase 数据库里查看合并状态:
select * from __all_zone;
select * from __all_zone where name = 'merge_status';
2、合并的过程
OceanBase 中最简单的 LSM-Tree 只有 C0 层(MemTable)和 C1 层(SSTable)。两层数据的合并过程如下:
① 将所有 observer 上的 MemTable 数据做大版本冻结(Major Freeze),其余内存作为新的 MemTable 继续使用;
② 将冻结后的 MemTable 数据合并(Merge)到 SSTable 中,形成新的 SSTable,并覆盖旧的 SSTable;
③ 合并完成后,冻结的 MemTable 内存才可以被清空并重新使用。
基于 LSM-Tree 的实践:转储
当 MemTable 的大小超过一定阈值时,就需要将 MemTable 中的数据转存到 SSTable 中以释放内存,我们将这一过程称之为转储。转储解决了合并操作引发的一系列问题,诸如:
资源消耗高,对在线业务性能影响较大
单个租户 MemStore 使用率高会触发集群级合并,其它租户成为受害者
合并耗时长,MemStore 内存释放不及时,容易造成 MemStore 满而数据写入失败的情况
1、分层转储
为了优化转储越来越慢的问题,引入了“分层转储”机制。分层转储采用错层 compaction 策略,新增 L0 层,被冻结的 MemTable 会直接 flush 为 Mini SSTable,可同时存在多个 Mini SSTable。L0 层是 size-tiered Compaction,内部继续根据不同场景分裂多层,L1 和 L2 层基于宏块粒度来维持 Leveled compaction。
每次转储会将 MemTable 数据与前一次转储的数据合并(Merge),转储文件最终会合并到 SSTable 中。
2、转储的触发方式
转储有两种触发方式:自动触发与手动触发。
当一个租户的 MemTable 内存的使用量达到 memstore_limit_percentage * freeze_trigger_percentage 所限制使用的值时,就会自动触发冻结(转储的前置动作),然后系统内部再调度转储。
也可以通过以下的运维命令手动触发转储。
ALTER SYSTEM MINOR FREEZE
[{TENANT[=] (‘tt1' [, 'tt2'...]) | PARTITION_ID [=] 'partidx%partcount@tableid‘}]
[SERVER [=] ('ip:port' [, 'ip:port'...])];