存储引擎背景知识
在收集大家对进阶教程建议的过程中,有非常多用户希望能够更进一步了解相对深入一些的背景知识,以及关键技术的实现原理。因此专门在这里增加了一个小节,为大家简单介绍 OceanBase 的存储引擎的架构、编码压缩技术,以及几种降低编码对查询性能影响的能力。
本文中标红的内容,均为用户在使用 OceanBase 进行测试和生产过程中容易被忽略的问题,且忽略后往往可能会造成严重影响。希望大家能够重点关注。
OceanBase 的存储引擎架构
历史归档库一般都是写多读少的场景,OceanBase 的 LSM-Tree 存储引擎天生具有高效的写入性能。而且既能够通过旁路导入高效处理定期的批量数据同步,又能够承载一些实时数据同步和历史库数据修改的场景。
OceanBase 的存储引擎基于 LSM-Tree 架构,将数据分为基线数据(放在基线 SSTable 中)和增量数据(放在 MemTable / 转储的 SSTable 中)两部分。其中基线数据是只读的,一旦生成就不再被修改;增量数据支持读写。
OceanBase 数据库的 DML 操作插入、更新、删除等操作,首先写入内存里的 MemTable,所以在写入性能上就相当于内存数据库的写入性能,超级适合历史归档库写多读少的场景。
等到 MemTable 达到一定大小时转储到磁盘成为增量的 SSTable(上图中的转储 SSTable 部分),转储到磁盘上的过程是批量的顺序写,相比 B+ 树架构离散的随机写,还会大大提高写盘的性能。
当增量的 SSTable 达到一定规模的时候,会触发增量数据和基线数据的合并,把增量数据和基线数据做一次整合,基线数据在合并完成之后就不会发生变化了,直到下一次合并。同时每天凌晨的业务低峰期,系统也会自动进行每日合并。
但是 LSM-Tree 的架构也存在一个问题,就是读放大(上图中的右侧箭头向上的部分)。在进行查询时,需要分别对 SSTable 和 MemTable 进行查询,并将查询结果进行一次归并,然后再将归并后的查询结果返回 SQL 层。OceanBase 为了减小读放大带来的影响,在内存实现了多级的缓存,例如 Block Cache 和 Row cache,来避免对基线数据频繁的进行随机读。
对于历史库数据的定期跑批报表,和一些 ad-hoc 的分析型查询带来的大量数据扫描的需求,因为历史库中的增量数据较少,所以绝大多数数据都存储在基线的 SSTable 中,这时计算下推可以只扫描基线数据,绕过了 LSM Tree 架构常见的读放大问题。而且 OceanBase 支持在压缩数据上执行下推算子和向量化解码的压缩格式可以轻松地处理大量数据查询和计算。
对于大量历史数据存储的需求, OceanBase 的 SSTable 存储格式和数据编码压缩功能可以使 OceanBase 更轻松地支持超大容量的数据存储。而且高度压缩的数据和在同等硬件下更高效的查询性能也能够大幅度降低存储和计算的成本。
此外,企业可以选择将历史库所在的集群部署在更经济的硬件上,但是对数据库进行运维基本不需要感知数据编码与压缩的相关配置,应用开发也可以做到在线库和历史库使用完全相同的访问接口,简化应用代码和架构。
这些特点让越来越多的企业开始在历史库场景使用 OceanBase 进行降本增效的实践。OceanBase 也将继续不断在存储架构,降本增效方面做出更多的探索。
OceanBase 的数据压缩技术
OceanBase 支持无需感知数据特征的通用压缩 (compression) 和感知数据特征并按列进行压缩的数据编码 (encoding)压缩。这两种压缩方式是正交的,也就是说,可以对一个数据块先进行编码压缩,然后再进行通用压缩,以此来实现更高的压缩率。
OceanBase 中的通用压缩是在不感知微块内部数据格式的前提下,将整个微块通过通用压缩算法进行压缩,依赖通用压缩算法来检测并消除微块中的数据冗余。目 前 OceanBase 支持用户选择 zlib、 snappy、 zstd、 lz4 算法进行通用压缩。用户可以根据表的应用场景,通过 DDL 对指定表的通用压缩算法进行配置和变更。
说明:微块的概念和传统数据库的 page / block 的概念比较类似。但是借助 LSM-Tree 的特性,OceanBase 数据库的微块是做过压缩的且在压缩后是变长的数据块,微块的压缩前大小可以通过建表的时候指定 block_size 来确定。(是不是终于知道 show create table 时显示的 block_size 是啥含义了~)
这里要注意的一点是:如果把 block_size(默认是 16KB)设置成更大的数值,虽然肯定可以让压缩的效果变的更好,但是因为微块是数据文件读 IO 的最小单位,所以可能会在一定程度上影响读性能(不过当需要读微块中的一行数据时,只会对这一行数据进行解码,在一定程度上可以避免部分解压算法读一部分数据要解压整个数据块的计算放大)。而且如果微块被设置成很大的数值,在合并时就无法重用有数据更新的微块,而需要对微块进行重写,进而影响合并速度。 总之,调整 block_size 的大小有利有弊,请大家根据实际业务情况,一定要在了解 block_size 含义和影响的基础上,再对其进行修改!(建议最好不改,直接使用默认值~)
由于通用压缩后的数据块在读取进行扫描前需要对整个微块进行解压,会消耗一定 CPU 并带来 overhead。为了降低解压数据块对于查询性能的影响,OceanBase 将解压数据的动作交给异步 I/O 线程来进行,并按需将解压后的数据块放在 block cache 中。这样结合查询时对预读(prefetching)技术的应用,可以为查询处理线程提供数据块的流水线,消除解压带来的额外开销。