聊聊OceanBase合并和转储
相比于Oracle、MySQL等传统的数据库,OceanBase数据库的存储引擎采用 LSM-Tree 的架构,这种存储引擎和之前所使用到的堆结构或B+树结构有很大的差别,今天我们就来聊聊 LSM-Tree 存储引擎所引入的合并和转储相关的功能特点。
OB 存储引擎的分层结构
LSM-Tree 存储引擎分为静态基线数据 (SSTable) 和动态增量数据 (MemTable)。这种架构本质上是一个基线加增量的存储引擎,SSTable 中存储的基线数据是只读的,一旦生成就不能修改,这部分数据是保存在磁盘上的;MemTable 是增量数据,应用程序数据的增量修改都保存在 MemTable,这部分数据是保存在内存中的。内存的数据并不是持久的,为了防止意外宕机导致内存数据丢失,在写入 MemTable 时还会同步将变更写入到在线日志,通过 Paxos 协议来保障数据的一致性。
MemTable 中的数据是按照 BTree 和 HashTable 的方式组织的,数据以B树结构进行存储,B树索引中的数据都是有序的,能够更好的支持范围查询,但对于点查等单行查询的场景,B树需要进行大量的主键检索,性能会比较差。为了满足点查等更多场景的需要,还增加了哈希索引结构,实现对单行查询的优化。每次事务执行时,MemTable 会自动维护B树和哈希索引之间的数据一致性。
![[Pasted image 20240915090506.png]]
用户表每个分区管理数据的基本单元是 SSTable,当 MemTable 大小达到某个阈值后,OceanBase 会将 MemTable 冻结,然后将其中的数据转存于磁盘上,转储后的结构就称为 SSTable。
OceanBase 将磁盘切分为大小 2MB 的定长数据块,称之为宏块,宏块是数据文件写 I/O 的基本单位,也是转储合并以及复制迁移等任务的基本粒度单位。
在宏块内部数据被组织为多个默认 16KB 大小的变长数据块,称之为微块,微块是数据文件读 I/O 的最小单位。数据最终是存储在微块中的,每个微块在构建时都会更加用户指定的压缩算法进行压缩,读取时会自动将数据解压后放入数据缓存中。
OB 存储引擎的合并转储
内存空间终究是有限的,这也意味着 MemTable 不可能无限保存在内存中,为此 LSM-Tree 存储引擎提供了转储和合并的机制,当 MemTable 的大小超过一定阈值时,需要将 MemTable 中的数据转存到 SSTable 中以释放内存,这一过程称之为 转储。转储会生成新的 SSTable,当转储的次数超过一定阈值时,或者在每天的业务低峰期,系统会将基线 SSTable 与之后转储的增量 SSTable 给合并为一个 SSTable,这一过程称之为 合并。
合并
最简单的 LSM Tree 只有两层: C0 层 (MemTable) 和 C1 层 (SSTable),其合并过程如下:
- 将所有 OBServer 上的 MemTable 数据做大版本冻结,其余内存作为新的 MemTable 继续使用;
- 将冻结后的 MemTable 数据合并到 SSTable 中,形成新的的 SSTable,并覆盖旧的 SSTable;
- 合并完成后,冻结的 MemTable 内存才可以被清空并重新使用。
按照合并宏块的不同,合并可以细化为全量合并、增量合并和渐进合并三种方式,其中:
- 全量合并,是把所有的静态数据都读取出来,和动态数据合并。全量合并消耗 I/O 和 CPU 资源,合并时间长;
- 增量合并,只会读取被修改过的宏块,和动态数据合并,对于未修改过的宏块,直接重用;
- 渐进合并,每次全量合并一部分,若干轮次后整体数据被重写一遍。
为了满足不同场景的需求,OceanBase 支持多种方式的合并触发模式。
- 定时合并,可以结合业务系统的特点,指定合并的触发时间,如凌晨2点;
- 手工合并,可以在需要的时候手工触发合并,释放内存;
- 自动合并,当租户 MemStore 内存使用率达到 freeze_trigger_percentage 参数设定值,并且转储次数达到 minor_freeze_times 参数设定值,则触发自动合并。需要注意的是,手工触发的转储并不会增加 minor_freeze_times 的计数。
此外,在合并过程中还支持轮转合并,利用分布式多副本的特点,轮流为每份副本单独做合并。
转储
合并是集群性的全局操作,合并过程时间较长,系统整体的资源消耗也很高,对于业务侧影响比较大,因此 OceanBase 又引入了转储机制。每个 OBServer 节点的租户可以独立决定自己 MemTable 的冻结操作,主备分区不需要保持一致。转储过程中的主要操作如下:
- 将 MemTable 数据做小版本冻结后写到磁盘上单独的转储文件里,不与 SSTable 数据做合并;
- 转储文件写完之后,冻结的 MemTable 内存被清空并重新使用;
- 每次转储会将 MemTable 数据与前一次转储的数据合并,最终合并到 SSTable 中。
通过转储引入中间层,避免单个租户 MemStore 使用率过高触发集群级的全量合并,使得其他租户成为受害者。
转储的触发方式分为自动触发和手工触发两种。当 MemStore 内存使用率达到设定阈值,会触发自动冻结,然后系统内部再调度转储;此外也可以通过手工的方式发起转储动作,手工转储支持指定租户、分区和 OBServer 进行转储。
合并和转储相关的参数
为了便于大家查阅,相关的合并和转储参数汇总如下。
参数名称 | 参数描述 | 默认值 |
---|---|---|
minor_freeze_times | 控制两次合并之间的转储次数,设置为0表示关闭转储功能 | 100 |
minor_merge_concurrency | 并发做转储的分区个数 | 0,表示并发线程数为 min[10,cpu_cnt] |
freeze_trigger_percentage | 设定租户MemStore使用率阈值,达到设定值则触发合并 | 70,V4.0之后调整为20 |
major_freeze_duty_time | 指定每日定时合并的时间 | 02:00 |
enable_merge_by_turn | 是否开启轮转合并 | False |
enable_manual_merge | 是否开启手工合并 | False |
zone_merge_order | 指定自动轮转合并的合并顺序 | NULL |
zone_merge_timeout | 定义合并的超时时间 | 3h |
data_disk_usage_limit_percentage | 定义数据文件最大可写入的百分比 | 90 |
datafile_disk_percentage | 定义磁盘空间使用阈值 | 90 |