面对快速增长的在线数据,尤其在例如订单、交易、日志等场景,数据往往多呈现为流水型特征,写入一段时间后即不会再次访问或更新;对访问频率很低甚至为0的数据,其占用的在线业务库固态存储空间,造成了大量硬件资源浪费,堆高企业的IT成本。同时,传统数据归档方案往往是业务研发或 DBA 采用脚本或简单的同步工具进行,难以在并发和效率上有效控制,很容易对在线数据库产生影响,严重的甚至导致生产数据误删事故。
OceanBase通过智能化的历史库平台工具结合数据库存储高压缩比的能力,可帮助企业轻松完成冷数据归档,释放宝贵的存储资源,降低总成本,并对数据生命周期实现可视化管理和智能运维。此方案历经蚂蚁集团核心业务场景验证,交易支付历史库单实例数据超过 6PB,采用上百台大容量机械盘的低成本硬件支撑,磁盘水位自动均衡,平稳运行多年,节省了大量机器资源。
OceanBase 存储引擎技术为何能更好地支持历史库场景
OceanBase 存储引擎的数据库压缩功能在设计上希望能够在用户少感知、不感知存储格式的前提下,在不降低事务型负载性能的同时降低存储空间和存储成本,同时提升分析型负载的性能。这样的设计与历史库的设计不谋而合。高度压缩的数据既能够帮助历史库数据降低至少 50% 的存储成本,高效的写入查询和统一的配置接口又能够帮助业务增效。
对于历史库数据同步的需求, OceanBase 的 LSM-Tree 存储引擎天生具有高效的写入性能,既能够通过旁路导入高效处理定期的批量数据同步,又能够承载一些实时数据同步和历史库数据修改的场景。
对于历史库数据的定期跑批报表,和一些 ad-hoc 的分析型查询带来的大量数据扫描的需求,因为历史库中增量数据较少,所以绝大多数数据都存储在基线的 SSTable 中,这时计算下推可以只扫描基线数据,绕过了 LSM-Tree 架构常见的读放大问题。而且支持在压缩数据上执行下推算子和向量化解码的压缩格式可以轻松地处理大量数据查询和计算。
对于大量历史数据存储的需求, OceanBase 的 SSTable 存储格式和数据编码压缩功能可以使 OceanBase 更轻松地支持超大容量的数据存储。而且高度压缩的数据和在同等硬件下更高效的查询性能也能够大幅度降低存储和计算的成本。
此外,企业可以选择将历史库所在的集群部署在更经济的硬件上,但是对数据库进行运维基本不需要感知数据编码与压缩的相关配置,应用开发也可以做到在线库和历史库使用完全相同的访问接口,简化应用代码和架构。
这些特点让越来越多的企业开始在历史库场景使用 OceanBase 进行降本增效的实践。OceanBase 不断在存储架构,降本增效方面做出更多的探索。
OceanBase 存储引擎和数据压缩技术介绍
作为一款 HTAP 数据库产品, OceanBase 使用基于 LSM-Tree 架构的存储引擎,同时支持 OLTP 与 OLAP 负载,这种存储架构提供了优秀的数据压缩能力。在 OceanBase 中,增量数据会写入 clog 和 memtable 中, OceanBase 的 memtable 是内存中的 B+ 树索引,提供高效的事务处理能力。
memtable 会定期通过 compaction 生成硬盘持久化数据 sstable ,多层 sstable会采用 leveled compaction 策略进行增量数据重整。sstable 中数据块的存储分为两层,其中 2M 定长的数据块(宏块)作为 sstable 写入 I / O 的最小单元,存储在宏块中的变长数据块(微块)作为数据块压缩和读 I / O 的最小单元。
在这样的存储架构下, OceanBase 的数据压缩集中发生在 compaction 过程中 sstable 的写入时,数据的在线更新与压缩得到了解耦。批量落盘的特性使其采用更激进的压缩策略。OceanBase 从 2.0 版本开始引入了行列混存的微块存储格式( PAX ),充分利用了同一列数据的局部性和类型特征,在微块内部对一组行以列存的方式存储,并针对数据特征按列进行编码。变长的数据块和连续批量压缩的数据也可以让 OceanBase 通过同一个 sstable 中已经完成压缩的数据块的先验知识,对下一个数据块的压缩进行指导,在数据块中压缩尽量多的数据行,并选择更优的编码算法。
与部分在 schema 上指定数据编码的数据库实现不同, OceanBase 选择了用户不感知的数据自适应编码,在给用户带来更小负担的同时降低了存储成本,从历史库角度而言,用户也不需要针对历史库数据做出过多压缩与编码相关的配置调整。 OceanBase 之所以能够在事务性能和压缩率之间取得更好的平衡,都得益于 LSM-Tree 的存储架构。
当然, LSM-Tree 架构不是解决数据库压缩所有问题的万金油,如何通过数据压缩降低成本、提升性能是业界一直在讨论的话题。对 B+ 树类的存储引擎进行更高效的压缩也有很多探索,比如基于可计算存储硬件的工作,利用存储硬件内部的透明压缩能力对 B+ 树类存储引擎的数据压缩进行优化,使其写放大达到了接近 LSM-Tree 架构存储引擎的效果。但 LSM-tree 中内存数据页更新与数据块落盘解耦,和 sstable 数据紧凑排布的特点,使得 LSM-tree 相对 B+ 树类存储引擎,仍然更适合在对查询/更新带来更少负面影响的前提下实现更高效的数据压缩。
OceanBase的数据库压缩技术
OceanBase 支持不感知数据特征的通用压缩 ( compression ) 和感知数据特征并按列进行压缩的数据编码 ( encoding )。这两种压缩方式是正交的,也就是说,可以对一个数据块先进行编码,然后再进行通用压缩,来实现更高的压缩率。
OceanBase 中的通用压缩是在不感知微块内部数据格式的前提下,将整个微块通过通用压缩算法进行压缩,依赖通用压缩算法来检测并消除微块中的数据冗余。目前 OceanBase 支持用户选择 zlib 、 snappy 、 zstd 、 lz4 算法进行通用压缩。用户可以根据表的应用场景,通过 DDL 对指定表的通用压缩算法进行配置和变更。
由于通用压缩后的数据块在读取进行扫描前需要对整个微块进行解压,会消耗一定 CPU 并带来 overhead。为了降低解压数据块对于查询性能的影响, OceanBase 将解压数据的动作交给异步 I / O 线程来进行,并按需将解压后的数据块放在 block cache 中。这样结合查询时对预读 ( prefetching ) 技术的应用,可以为查询处理线程提供数据块的流水线,消除解压带来的额外开销。
通用压缩的优点是对被压缩的数据没有任何假设,任何数据都可能找到模式并压缩,但往往出于平衡压缩性能和压缩率的考虑,通用压缩算法会放弃对一些复杂数据冗余模式的探测和压缩。对于关系型数据库来说,系统对数据库内存储的结构化数据有着更多的先验知识,利用这些先验知识可以对数据进行更高效的压缩。
OceanBase 的数据编码算法
当通过一列数据存储城市、性别、产品分类等具有类型属性的值时,这些列数据块内部数据的基数( cardinality )也会比较小,这时数据库可以直接在用户数据字段上建立字典,来实现更高的压缩率;
当数据按时序插入数据库,这些插入的数据行中的时间相关字段、自增序列等数据的值域会相对较小,也会有单调递增等特性,利用这些特性,数据库可以更方便地为这些数据做 bit-packing 、差值等编码。
为了实现更高的压缩比,帮助用户大幅降低存储成本, OceanBase 设计了多种编码算法,最终在 OceanBase 的负载上实现了很好的压缩效果。 OceanBase 根据实际业务场景需求实现了单列数据的 bit-packing 编码、字符串 HEX 编码、字典编码、 RLE 编码、常量编码、数值差值编码、定长字符串差值编码,同时,创新地引入了列间等值编码和列间子串编码,能够分别对数据库中一列数据或几列数据间可能产生的不同类型数据冗余进行压缩。
降低存储的位宽:Bit-packing 和 HEX 编码
Bit-packing 和 HEX 编码类似,都是在压缩数据的基数较小时,通过更小位宽的编码来表示原数据。而且这两种编码可以与其他编码叠加,对于其他编码产生的数值或字符串数据,都可以再通过 bit-packing 或 HEX 编码进一步去除冗余。
(bit-packing)
( HEX 编码)
单列数据去重:字典编码和 RLE 编码等
字典编码则可以通过在数据块内建立字典,来对低基数的数据进行压缩。当低基数的数据在微块内的分布符合对应的特征时,也可以使用游程编码/常量编码等方法进行进一步的压缩。
(字典编码/RLE 编码)
利用数据的值域压缩:差值编码等
差值编码也是常用的编码方法, OceanBase 中的差值编码分为数值差值编码和定长字符串差值编码。数值差值编码主要用来对值域较小的数值类数据类型进行压缩。对于日期、时间戳等数据,或其他临近数据差值较小的数值类数据,可以只存储最小值,每行存储原数据与最小值的差值。定长字符串编码则可以比较好地对人工生成的 ID,如订单号/身份证号、url 等有一定模式的字符串进行压缩,对一个微块的数据存储一个模式串,每行额外存储与模式串不同的子串差值,来达到更好的压缩效果。
(整形差值)
(字符串差值)
减小多列数据冗余:列间编码
为利用不同列间数据的相似性增强压缩效果,OceanBase 引入了列间编码。通常情况下,列存数据库只会对数据在列内部进行编码,但在实际应用中有很多表除了同一列数据之间存在相似性,不同列的数据之间也可能有一定的关系,利用这种关系可以通过一列数据表示另外一列数据的部分信息。
列间编码可以对复合列、系统生成的数据做出更好的压缩,也能够降低在数据表设计范式上的问题导致的数据冗余。
自适应压缩技术:让数据库选择编码算法
数据编码的压缩效果不仅与表的 schema 相关,同时还与数据的分布,微块内数据值域等数据本身的特征相关,这也就意味着比较难以在用户设计表数据模型时指定列编码来实现最好的压缩效果。为了减轻用户的使用负担,也为了实现更好的压缩效果,OceanBase 支持在合并过程中分析数据类型、值域、NDV 等特征,结合 compaction 任务中上一个微块对应列选择的编码算法和压缩率自适应地探测合适的编码,对同一列在不同数据块中支持使用不同的算法来进行编码,也保证了选择编码算法的开销在可接受的区间内。
降低数据编码对查询性能影响的能力
为了能够更好地平衡压缩效果和查询的性能,我们在设计数据编码格式时也考虑到了对查询性能带来的影响。
行级粒度数据随机访问
通用压缩中如果要访问一个压缩块中的一部分数据通常需要将整个数据块解压后访问,某些分析型系统的数据编码大多面向扫描的场景,点查的场景比较少,因此采用了在访问某一行数据时需要对相邻数据行或数据块内读取行之前所有行进行解码计算的数据编码的格式(如 PFor 等差值编码)。
OceanBase 需要更好地支持事务型负载,就意味着要支持相对更高效的点查,因此 OceanBase 在设计数据编码格式时保证了编码后的数据是可以以行为粒度随机访问的。也就是在对某一行进行点查时只需要对这一行相关的元数据进行访问并解码,减小了随机点查时的计算放大。同时对于编码格式的微块,解码数据所需要的所有元数据都存储在微块内,让数据微块有自解释的能力,也在解码时提供了更好的内存局部性。
缓存解码器
在 OceanBase 目前的数据解码实现中,每一列数据都需要初始化一个解码器对象来解析数据,构造解码器时会需要进行一些计算和内存分配,为了进一步减小访问编码数据时的 RT ,OceanBase 会将数据的解码器和数据块一起缓存在 block cache 中,访问 cache 中数据块时可以直接通过缓存的解码器解析数据。当不能命中 block cache 中缓存的解码器时,OceanBase 还会为解码器用到的元数据内存和对象构建缓存池,在不同查询间复用这些内存和对象。
通过上述细节上的优化,行列混存格式的 sstable 编码数据也可以很好地支持事务型负载。而且由于编码数据行列混存的格式,使得在分析型查询的处理上,编码数据有着和列存数据相似的特性,数据分布更紧凑,对 CPU cache 更加友好。这些特性使列存常用的优化手段也能应用于分析型查询优化中,充分利用 SIMD 等方法来提供更高效的分析型负载处理。
计算下推
由于编码数据中会存储有序字典、 null bitmap 、常量等可以描述数据分布的元数据,在扫描数据时可以利用这些数据对于部分过滤,聚合算子的执行过程进行优化,实现在未解码的数据上直接进行计算。 OceanBase 对分析处理能力进行了大幅的优化,其中包括聚合与过滤计算下推到存储层执行,和在向量化引擎中利用编码数据的列存特征进行向量化的批量解码等特性。在查询时充分利用了编码元数据和编码数据列存储的局部性,在编码数据上直接进行计算,大幅提高了下推算子的执行效率和向量化引擎中的数据解码效率。基于数据编码的计算下推和向量化解码也成为了支持 OceanBase 高效处理分析型负载,在 TPC-H benchmark 中达到优秀性能指标的重要功能。
据编码压缩的基础测试
不同的压缩方式如何影响 OceanBase 的压缩效果,以下通过一个简单的测试进行观察。使用 OceanBase 4.0 版本分别在交易场景的 TPC-H 10g 的数据模型和用户行为日志场景的 IJCAI-16 Brick-and-Mortar Store Recommendation Dataset 数据集上对 OceanBase 的压缩率进行测试。
TPC-H 是对订单,交易场景的建模,对 TPC-H 模型中数据量比较大的两张表,即存储订单的 ORDERS 表和存储商品信息的 LINEITEM 表的压缩率进行统计。在 OceanBase 默认配置( zstd + encoding )下,这两张表的压缩率可以达到 4.6 左右,相较只开启 encoding 或 zstd 压缩时提升明显。
IJCAI-16 taobao user log 则是淘宝脱敏后的真实业务数据,存储了用户浏览商品时的行为日志。在 OceanBase 默认配置( zstd + encoding )下压缩率可以达到 9.9 ,只开启 encoding 压缩率可以达到 8.3 ,只开启 zstd 压缩率为 6.0 。