作者:曲山,阿里云资深技术专家
X-Engine是阿里自研的数据库存储引擎,曾一度登上过数据库业界最顶尖的会议SIGMOD,作为RDS MySQL的存储引擎,X-Engine到底有何魅力呢?
1
X-Engine是什么
X-Engine是阿里自研的数据库存储引擎,可以作为MySQL的存储引擎使用,兼容MySQL的功能,目前已经广泛应用在阿里集团内部诸多业务系统中。只需要在配置中将默认存储引擎设置为X-Engine,在后续过程中所有创建的表就可以使用了,当然也可以在创建表时指定存储引擎,这样只有指定的表才会使用X-Engine.
2
为什么设计一个新的存储引擎
X-Engine是阿里内部生长出来的,一开始,也是为应对阿里内部业务带来的挑战,早在2010年,阿里内部就大规模部署了MySQL数据库,但是业务量的逐年爆炸式增长,对数据库提出了严苛的要求,一是极高的并发事务处理能力,尤其是双十一的流量突发式暴增;二是数据规模超大,需要占用大量存储资源。这两个问题当然都可以扩展数据库节点的分布式方案解决,不过堆机器不是一个高效的手段,我们更想用技术的手段来将单机的数据库性价比提升到极致,达到以少量资源换取性能大幅上升的目的。传统数据库架构下的性能已经被仔细的研究过,数据库领域的泰斗,图灵奖得主Michael Stonebreaker就此写过一篇论文<OLTP Through the Looking Glass, and What We Found There>,分析指出传统关系通用型数据库,仅仅有不到百分之十左右的时间是在做真正有效的处理数据工作。剩下百分之九十多的时间都浪费在其它工作上,比如一些加锁等待,缓冲管理,日志同步等。
3
整体架构
为此我们设计了全新架构的存储引擎X-Engine,得益于MySQL Pluginable Storage Engine的特性,X-Engine可以无缝对接兼容MySQL特性,我们只需要专注优化存储结构就好。X-Engine使用了一种对数据进行分层的存储架构,(如下图) 因为目标是面向大规模的海量数据存储,提供高并发事务处理能力和尽可能降低成本,我们观察到,大部分大数据量场景下,数据被访问的机会是不均等的,访问频繁的热数据实际上占比很少。X-Engine根据数据访问频度(冷热)的不同将数据划分为多个层次,针对每个层次数据的访问特点,设计对应的存储结构,写入合适的存储设备。X-Engine使用了LSM-Tree作为分层存储的架构基础,并在这之上进行了重新设计。简单来讲,热数据层和数据更新使用内存存储,利用了大量内存数据库的技术(Lock-Free index structure/append only)提高事务处理的性能。我们设计了一套事务处理流水线处理机制,把事务处理的几个阶段并行起来,极大提升了吞吐。而访问频度低的冷(温)数据逐渐淘汰或是合并到持久化的存储层次中,结合当前丰富的存储设备层次体系(NVM/SSD/HDD)进行存储。我们对性能影响比较大的compaction过程做了大量优化,主要是拆分数据存储粒度,利用数据更新热点较为集中的特征,尽可能的在合并过程中复用数据,精细化控制LSM的形状,减少I/O和计算代价,并同时极大的减少了合并过程中的空间放大。同时使用更细粒度的访问控制和缓存机制,优化读的性能。
4
技术特点
X-Engine基于LSM-Tree架构设计,主要是为了利用其天然分层的结构,同时为了避免LSM固有的一些劣势,对整个存储架构做了根本性的调整和优化,比如 :
- 使用多事务处理队列和流水线处理技术,减少线程上下文切换代价,并计算每个阶段任务量配比,使整个流水线充分流转,极大提升事务处理性能,相对于其他类似架构的存储引擎比如RocksDB,X-Engine的事务处理性能有10倍以上提升。
- X-Engine使用的copy-on-write技术,避免原地更新数据页,从而对只读数据页面进行编码压缩,相对于传统存储引擎(比如InnoDB)数据压缩2倍以上。
- 数据复用技术减少数据合并代价,并且因为数据复用减少缓存淘汰带来的性能抖动。进一步利用FPGA硬件加速compaction过程,使得系统上限进一步提升。这个技术也属首次将硬件加速技术应用到在线事务处理数据库存储引擎中,我们也将其总结为论文已经被今年的顶级会议FAST’20接收.
- Bloom Filter 快速判定数据是否存在, Surf Filter判断范围数据是否存在, Row Cache缓存热点行,加速读取性能。
以下章节逐一介绍X-Engine的优化架构和实现细节。既然是基于LSM架构设计,首先简要介绍下LSM架构的一些特点。
5
背景知识:LSM基本逻辑
一条数据在LSM结构中的旅程,从写入WAL(Write Ahead Log)开始,然后进入MemTable,这是Ta整个生命周期的第一处落脚点。随后,flush操作将Ta刻在更稳固的介质上,compaction操作将Ta带往更深远的去处,或是在途中丢弃,取决于Ta的继任者何时到来。LSM的本质是,所有写入操作并不做原地更新,而是以追加的方式写入内存。每次写到一定程度,即冻结为一层(Level),写入持久化存储。所有写入的行,都以主键(Key)排序好后存放,无论是在内存中,还是持久化存储中。在内存中即为一个排序的内存数据结构(Skiplist, B-Tree, etc.),在持久化存储也作为一个只读的全排序持久化存储结构。普通的存储系统若要支持事务处理,尤其是ACI,需要加入一个时间维度,借此为每个事务构造出一个不受并发干扰的独立视域。存储引擎会对每个事务定序并赋予一个全局单调递增的事务版本号(SN),每个事务中的记录会存储这个SN以判断独立事务之间的可见性,从而实现事务的隔离机制。如果LSM存储结构持续写入,不做其他的动作,那么最终会成为如下结构:注意这里每一层的SN范围标识了事务写入的先后顺序,已经持久化的数据不再会被修改。每一层数据按Key排序,层与层之间的Key range会交叠。
6
X-Engine:高度优化的LSM
上面是LSM宏观逻辑结构,如果具体来论读写操作和compaction如何进行,就需要探讨每一层的数据组织方式,每个LSM变种的实现各不相同。X-Engine的memtable使用了Locked-free SkipList. 求的是简单,而且并发读写的性能都比较高。当然有更高效的数据结构,或者同时使用多种索引技术。这个部分X-Engine没有做过多优化,原因在事务处理的逻辑比较复杂,写入内存表还没有成为其瓶颈。持久化层如何组织更显高效,这就需要讨论每层的细微结构。
1、数据组织
简单来说,X-Engine的每层都划分成固定大小的Extent,存放每个层次中的数据的一个连续片段(Key Range). 为了快速定位Extent,为每层Extents建立了一套索引(Meta Index),所有这些索引,加上所有的memory tables(active/immutable)一起组成了一个元数据树(Metadata Tree),root节点为"Metadata Snapshot", 这个树结构类似于B-Tree,当然不尽相同。
可以看到"Metadata Snapshot 2"相对于"Metadata Snapshot 1"并没有太多的变化,仅仅修改了发生变更的一些叶子节点以及索引节点。这个技术颇有些类似"B-trees, Shadowing, and Clones",如果你读过那篇论文,会对理解这个过程有所帮助。
2、事务处理
得益于LSM轻量化写机制,写入操作固然是其明显的优势,但是事务处理远不只是把更新的数据写入系统那么简单,这里要保证ACID,涉及到一整套复杂的流程。X-Engine将整个事务处理过程分为两个阶段:读写阶段和提交阶段。读写阶段需要校验事务的写写冲突,读写冲突,判断事务是否可以执行或回滚重试,或是等锁。如果事务冲突校验通过,则把修改的所有数据写入"Transaction Buffer", 提交阶段包括写WAL,写内存表,以及提交并返回给用户结果的整个过程,这里面既有I/O操作(写日志,返回消息),也有CPU操作(拷贝日志,写内存表)。为了提高事务处理吞吐,系统内会有大量事务并发执行,单个I/O操作比较昂贵,大部分存储引擎会倾向于聚集一批事务一起提交,称为"Group Commit",能够合并I/O操作,但是一组事务提交的过程中,还是有大量等待过程的,比如写入日志到磁盘过程中,除了等待落盘无所事事。X-Engine为了进一步提升事务处理的吞吐,采用了一种流水线的技术:把提交阶段分为四个独立的更细的阶段:拷贝日志到缓冲区(Log Buffer), 日志落盘(Log Flush), 写内存表(Write memtable), 提交返回(Commit)。我们的事务提交线程到了处理阶段,都可以自由选择执行流水线中任意一个阶段,这样每个阶段都可以并行起来,只要流水线任务的大小划分得当,就能充分并行起来,流水线处于接近满载状态。另外,利用的是事务处理的线程,而非后台线程,每个线程在执行的时候,要么选择了流水线中的一个阶段干活,要么逛了一圈发现无事可做,干脆回去接收更多的请求,这里没有等待,也无需切换,充分的调动了每个线程的能力。
读操作中最核心的是缓存设计,Row Cache来应付单行查询,Block Cache负责Row Cache miss的漏网之鱼,也用来应付scan;由于LSM的compaction操作会一次大批量更新大量的Data Block,导致Block Cache中大量数据短时间内失效,带来性能的急剧抖动。X-Engine同样做了很多的处理:
1.减少Compaction的粒度,。
2. 减少compaction过程中改动的数据(见稍后章节) 3. compaction过程中针对已有的cache数据做定点更新。由此可以基本将cache失效带来的抖动降到最低的水平。
7
可以看出,对于数据复用的过程是在逐行迭代的过程中完成的,不过这种精细的数据复用带来另一个副作用,即数据的碎片化,所以在实际操作的过程中也需要根据实际情况进行折中。
数据复用不仅给compaction操作本身带来了好处,降低操作过程中的I/O与CPU消耗,更对系统的综合性能产生了一系列的影响。比如compaction过程中数据不用完全重写,大大减少了写入空间放大; 更因为大部分数据保持原样,数据缓存不会因为数据更新而失效,减少合并过程中因缓存失效带来的读性能抖动。实际上,优化compaction的过程只是X-Engine工作的一部分,还有更重要的,就是优化compaction调度的策略,选什么样的Extent,定义compaction任务的粒度,执行的优先级,都会对整个系统性能产生影响,可惜并不存在什么完美的策略,X-Engine积累了一些经验,定义了很多规则,而探索如何合理的调度策略是未来一个重要方向。
8
什么时候你应该选择使用X-Engine
X-Engine一直以来的目标都是为了成为MySQL生态体系下大数据体量通用存储引擎,我们还在持续优化存储结构,压缩算法,读写性能,稳定性,以期达到最好的性价比。X-Engine仍然有他最擅长的方向,可以作为在线历史数据一体化的数据库,在不损失读写性能的情况下充分压缩数据表,根据我们的测试结果,在标准TPC-C测试场景下,X-Engine的tpmC性能与InnoDB基本持平。如果你的应用使用MySQL数据库,有大量写入,希望大幅降低存储成本,并且有一定查询需求的应用,例如日志、消息归档,订单流水存储等等,都非常适合使用X-Engine.X-Engine不只是一个为研究设计的系统,从一开始就是为了实现用户价值,在阿里集团内部大规模使用已经有两年多,并且在最为核心的交易,钉钉消息历史库上都全面替代了原有的系统,达到了预期中的良好效果,为交易历史库(原来使用HBase)节省33%成本,为钉钉消息历史库(原来使用MySQL with InnoDB)节省了60%的成本。更多信息请参考X-Engine RDS最佳应用实践。9
如何在MySQL RDS中使用X-Engine
X-Engine目前只在RDS MySQL 8.0版本中提供,当前使用RDS MySQL 5.6/5.7的用户如果想使用X-Engine引擎,请迁移至RDS MySQL 8.0版本,同时配置X-Engine引擎生效,你可以设置默认存储引擎为X-Engine,也可以在创建表时显示指定表存储引擎为X-Engine,与其他存储引擎混合使用,也可以通过alter table your_table engine = xengine来将已有的表转换为X-Engine存储(注意此操作会锁表并拷贝数据,视数据存量大小需要时间不等)。具体的操作,参数配置,以及使用限制,请参见X-Engine使用文档。
10