OceanBase 数据库的存储引擎基于 LSM Tree 架构,将数据分为静态基线数据(放在 SSTable 中)和动态增量数据(放在 MemTable 中)两部分,其中 SSTable 是只读的,一旦生成就不再被修改,存储于磁盘;MemTable 支持读写,存储于内存。数据库 DML 操作插入、更新、删除等首先写入 MemTable,等到 MemTable 达到一定大小时转储到磁盘成为 SSTable。在进行查询时,需要分别对 SSTable 和 MemTable 进行查询,并将查询结果进行归并,返回给 SQL 层归并后的查询结果。同时在内存实现了 Block Cache 和 Row cache,来避免对基线数据的随机读。
当内存的增量数据达到一定规模的时候,会触发增量数据和基线数据的合并,把增量数据落盘。同时每天晚上的空闲时刻,系统也会自动每日合并。
OceanBase 数据库本质上是一个基线加增量的存储引擎,在保持 LSM-Tree 架构优点的同时也借鉴了部分传统关系数据库存储引擎的优点。
传统数据库把数据分成很多页面,OceanBase 数据库也借鉴了传统数据库的思想,把数据文件按照 2MB 为基本粒度切分为一个个宏块, 每个宏块内部继续拆分出多个变长的微块; 而在合并时数据会基于宏块的粒度进行重用, 没有更新的数据宏块不会被重新打开读取, 这样能够尽可能减少合并期间的写放大, 相较于传统的 LSM-Tree 架构数据库显著降低合并代价。另外,OceanBase 数据库通过轮转合并的机制把正常服务和合并时间错开,使得合并操作对正常用户请求完全没有干扰。
由于 OceanBase 数据库采用基线加增量的设计,一部分数据在基线,一部分在增量,原理上每次查询都是既要读基线,也要读增量。为此,OceanBase 数据库做了很多的优化,尤其是针对单行的优化。OceanBase 数据库内部除了对数据块进行缓存之外,也会对行进行缓存,行缓存会极大加速对单行的查询性能。对于不存在行的"空查",我们会构建布隆过滤器,并对布隆过滤器进行缓存。OLTP 业务大部分操作为小查询,通过小查询优化,OceanBase 数据库避免了传统数据库解析整个数据块的开销,达到了接近内存数据库的性能。另外,由于基线是只读数据,而且内部采用连续存储的方式,OceanBase 数据库可以采用比较激进的压缩算法,既能做到高压缩比,又不影响查询性能,大大降低了成本。
结合借鉴经典数据库的部分优点,OceanBase 数据库提供了一个更为通用的 LSM-tree 架构的关系型数据库存储引擎, 具备以下特性:
低成本,利用 LSM-tree 写入数据不再更新的特点, 通过自研行列混合编码叠加通用压缩算法, OceanBase 数据库的数据存储压缩率能够相较传统数据库提升 10+ 倍。
易使用,不同于其他 LSM-tree 数据库,OceanBase 数据库通过支持活跃事务的落盘保证用户的大事务/长事务的正常运行或回滚,多级合并和转储机制来帮助用户在性能和空间上找到更佳的平衡。
高性能,对于常见的点查,OceanBase 数据库提供了多级 cache 加速来保证极低的响应延时,而对于范围扫描,存储引擎能够利用数据编码特征支持查询过滤条件的计算下压,并提供原生的向量化支持。
高可靠,除了全链路的数据检验之外,利用原生分布式的优势,OceanBase 数据库还会在全局合并时通过多副本比对以及主表和索引表比对的校验来保证用户数据正确性,同时提供后台线程定期扫描规避静默错误。
存储引擎的功能
从功能模块划分上,OceanBase 数据库存储引擎可以大致分为以下几个部分。
数据存储
数据组织
和其他 LSM-tree 数据库一样,OceanBase 数据库也将数据分为内存增量数据(MemTable)和存储静态数据(SSTable)两个层次,其中 SSTable 是只读的,一旦生成就不再被修改,存储于磁盘;MEMTable 支持读写,存储于内存。数据库 DML 操作插入、更新、删除等首先写入 MEMTable,等到 MEMTable 达到一定大小时转储到磁盘成为 SSTable。
另外在 OceanBase 数据库内,SSTable 会继续细分为 Mini SSTable、Minor SSTable、Major SSTable 三类,MEMTable 转储后形成的我们称为 Mini SSTable,多个 Mini SSTable 会定期 compact 成为 Minor SSTable,而当 OceanBase 数据库特有的每日合并开始后,每个分区所有的 Mini SSTable 和 Minor SSTable 会整体合并为 Major SSTable。
存储结构
在 OceanBase 数据库中, 每个分区的基本存储单元是一个个的 SSTable,而所有存储的基本粒度是宏块,数据库启动时,会将整个数据文件按照 2MB 定长大小切分为一个个宏块,每个 SSTable 实质就是多个宏块的集合。
每个宏块内部又会继续切分为多个微块,微块的概念和传统数据库的 page/block 概念比较类似, 但是借助 LSM-Tree 的特性,OceanBase 数据库的微块是做过压缩变长的,微块的压缩前大小可以通过建表的时候指定
block_size
来确定。而微块根据用户指定存储格式可以分别以 encoding 格式或者 flat 格式存储,encoding 格式的微块, 内部数据会以行列混合模式存储;对于 flat 格式的微块,所有数据行则是平铺存储。
压缩编码
OceanBase 数据库对于微块内的数据会根据用户表指定的模式分别进行编码和压缩。当用户表打开 encoding 时, 每个微块内的数据会按照列维度分别进行列内的编码,编码规则包括字典/游程/常量/差值等,每一列压缩结束后,还会进一步对多列进行列间等值/子串等规则编码。编码不仅能帮助用户对数据进行大幅压缩,同时提炼的列内特征信息还能进一步加速后续的查询速度。
在编码压缩之后,OceanBase 数据库还支持进一步对微块数据使用用户指定的通用压缩算法进行无损压缩,进一步提升数据压缩率。
转储合并
转储
OceanBase 数据库中的转储即 Minor Compaction 概念可以理解和其他 LSM-tree 架构数据库的 Compaction 概念类似,主要负责 MEMTable 刷盘转成 SSTable 以及多个 SSTable 之间的 Compaction 策略选择以及动作。OceanBase 数据库中采用的是 leveled 结合 size tired 的 Compaction 策略,大致可以分为三层,其中 L1 和 L2 就是固定的 leveled 层次,L0 层是 size tired,L0 内部还会继续根据写放大系数以及 SSTable 个数进行内部 Compaction 动作。
合并
合并也就是 Major Compaction,在 OceanBase 数据库中也叫每日合并,概念和其他 LSM-Tree 数据库稍有不同。顾名思义,这个概念诞生之初是希望这个动作放到每天凌晨 2 点左右整个集群做一次整体的 Compaction 动作。合并一般是由 RS 根据写入状态或者用户设置发起调度,每次合并都会选取一个全局的快照点,集群内所有的分区都会用这个快照点的数据做一次 Major Compaction,这样每次合并集群所有的数据都基于这个统一的快照点生成相应的 SSTable,通过这个机制不仅能帮助用户定期整合增量数据,提升读取性能,同时还提供了一个天然的数据校验点,通过全局的一致位点,OceanBase 数据库能够在内部对多副本以及主表索引表进行多维度的物理数据校验。
查询读写
插入
在 OceanBase 数据库中,所有的数据表都可以看成索引聚簇表,即使是无主键堆表,在内部也会为其维护一个隐藏主键。因此当用户插入数据时,在向 MEMTable 中写入新的用户数据前,需要先检查当前数据表中是否已经存在相同数据主键数据,为了加速这个重复主键查询性能,对于每个 SSTable 会由后台线程针对不同宏块判重频率来异步调度构建 Bloomfilter。
更新
作为 LSM-Tree 数据库,OceanBase 数据库中的每次更新同样会插入一行新数据,和 Clog 不同, 在 MEMTable 中更新写入的数据只包含更新列的新值以及对应的主键列,即更新行并不一定包含表全部列的数据,在不断的后台 Compaction 动作中,这些增量更新会不断的融合在一起加速用户查询
删除
和更新类似,删除操作同样不是直接作用在原数据上,而是使用删除行的主键写入一行数据,通过行头标记来标明删除动作。大量的删除动作对于 LSM-Tree 数据库都是不友好的,这样会导致即使一个数据范围被完全删除后,数据库还是需要迭代这个范围内所有删除标记行,再做完熔合后才能确认删除状态。针对这个场景,OceanBase 数据库提供了内在的范围删除标记逻辑来规避这种场景,另外也支持让用户显示指定表模式,这样可以通过特殊的转储合并方式来提前回收这些删除行加速查询。
查询
由于增量更新的策略,查询每一行数据的时候需要根据版本从新到旧遍历所有的 MEMTable 以及 SSTable,将每个 Table 中对应主键的数据熔合在一起返回。数据访问过程中会根据需要利用 Cache 加速,同时针对大查询场景,SQL 层会下压过滤条件到存储层,利用存数据特征进行底层的快速过滤,并支持向量化场景的批量计算和结果返回。
多级缓存
为提升性能,OceanBase 数据库支持了多级的缓存系统,对于查询提供针对数据微块的 Block Cache,针对每个 SSTable 的 Row Cache,针对查询熔合结果的 Fuse Row Cache,针对插入判空检查的 bloomfilter cache 等,同一个租户下的所有缓存共享内存,当 MEMTable 写入速度过快时,可以灵活的从当前各种缓存对象中挤占内存给写入使用。
数据校验
作为金融级关系数据库,OceanBase 数据库一直将数据质量和安全放在第一位,全数据链路每一个涉及持久化的数据部分都会增加数据校验保护,同时利用多副本存储的内在优势,还会增加副本间的数据校验进一步验证整体数据一致。
逻辑校验
在常见部署模式下,OceanBase 数据库的每个用户表在集群中都会存在多副本,在集群每日合并时, 所有的副本都会基于全局统一的快照版本生成一致的基线数据,利用这个特性,所有副本的数据会在合并完成时比对数据的校验和,保证完全一致。更进一步,基于用户表的索引,还会继续比对索引列的校验和,确保最后返回用户的数据不会因为程序内在问题出错。
物理校验
针对数据存储,OceanBase 数据库从数据存储最小 I/O 粒度微块开始,在每个微块/宏块/SSTable/ 分区上都记录了相应的校验和,每次数据读取时都会进行数据校验;为了防止底层存储硬件问题,在转储合并写入数据宏块时也会在写入后马上重新数据进行校验;最后每个 Server 后台会有定期的数据巡检线程对整体数据扫描校验,以提前发现磁盘静默错误。