关于数据库:如何基于LSMtree架构实现一写多读

3次阅读

共计 13894 个字符,预计需要花费 35 分钟才能阅读完成。

简介:传统 MySQL 基于 binlog 复制的主备架构有它的局限性,包含存储空间无限,备份复原慢,主备复制提早等问题,为了解决用户对于云上 RDS(X-Engine)大容量存储,以及弹性伸缩的诉求,PolarDB 推出了历史库 (基于 X -Engine 引擎的一写多读) 产品,反对物理复制,提供一写多读的能力,目前曾经在阿里云官网售卖。本文次要论述如何基于 LSM-tree 构造的存储引擎实现数据库的一写多读能力。

作者 | 雁闲
起源 | 阿里技术公众号

一 前言

PolarDB 是阿里巴巴自研的新一代云原生关系型数据库,在存储计算拆散架构下,利用了软硬件联合的劣势,为用户提供具备极致弹性、海量存储、高性能、低成本的数据库服务。X-Engine 是阿里巴巴自研的新一代存储引擎,作为 AliSQL 的外围引擎之一已宽泛用于阿里巴巴团体外围业务,包含交易历史库,钉钉历史库,图片空间等。X-Engine 基于 LSM-tree 架构,其外围特色是数据以追加写形式写入,高压缩低成本,实用于写多读少,有低成本诉求的业务场景。传统 MySQL 基于 binlog 复制的主备架构有它的局限性,包含存储空间无限,备份复原慢,主备复制提早等问题,为了解决用户对于云上 RDS(X-Engine)大容量存储,以及弹性伸缩的诉求,PolarDB 推出了历史库 (基于 X -Engine 引擎的一写多读) 产品,反对物理复制,提供一写多读的能力,目前曾经在阿里云官网售卖。本文次要论述如何基于 LSM-tree 构造的存储引擎实现数据库的一写多读能力。

二 LSM-tree 数据库引擎

LSM-Tree 全称是 Log Structured Merge Tree,是一种分层,有序,面向磁盘设计的数据结构,其核心思想是利用磁盘批量的程序写要比随机写性能高的特点,将所有更新操作都转化为追加写形式,晋升写入吞吐。LSM-tree 类的存储引擎最早源于 Google 三驾马车之一的 BigTable 的存储引擎以及它的开源实现 LevelDB。LSM-tree 存储引擎有几个特点,首先增量数据像日志一样,通过追加形式写入,程序落盘;其次,数据依照 key 来进行有序组织,这样在内存和磁盘中会造成一颗颗小的“有序树”;最初,各个“有序树”能够进行归并,将内存中的增量数据迁徙到磁盘上,磁盘上的多个“有序树”能够进行归并,优化树的形态,整个 LSM-tree 是一个有序的索引组织构造。

在云原生数据库时代,一写多读技术已被广泛应用于生产环境中,次要云产商都有其标杆产品,典型代表包含亚马逊的 Aurora,阿里云的 PolarDB 以及微软云的 Socrates。它的核心思想是计算存储拆散,将有状态的数据和日志下推到分布式存储,计算节点无状态,多个计算节点共享一份数据,数据库能够低成本疾速扩大读性能。Aurora 是这个畛域的开山鼻祖,实现了业内第一个一写多读的数据库,计算节点 Scale up,存储节点 Scale out,并将日志模块下推到存储层,计算节点之间,计算与存储节点之间传输 redo 日志,计算节点基于 Quorum 协定写多正本保障可靠性,存储层提供多版本页服务。PolarDB 与 Aurora 相似,也采纳了存储计算拆散架构,与 Aurora 相比,PolarDB 它本人的特色,存储基座是一个通用的分布式文件系统,大量采纳 OS-bypass 和 zero-copy 技术,存储的多正本一致性由 ParallelRaft 协定保障。PolarDB 计算节点与存储节点同时传输数据页和 redo 日志,计算节点与计算节点之间只传递位点信息。与 Aurora 的“日志即数据库”理念一样,Socrates 的节点之间只传输 redo 日志,也实现了多版本页服务,它的特点是将数据库存储层的持久性与可用性离开,形象出一套日志服务。整个数据库分为 3 层,一层计算服务,一层 page server 服务和一层日志服务,这样设计的益处是能够分层进行优化,提供更灵便和细粒度的管制。

尽管 Aurora,PolarDB 和 Socrates 在设计上各有特点,但它们都独特践行了存储计算拆散思维,数据库层面提供一写多读的能力。深刻到存储引擎这一层来说,这几个产品都是基于 B +tree 的存储引擎,如果基于 LSM-tree 存储引擎来做呢?LSM-tree 有它本人的特点,追加程序写,数据分层存储,磁盘上数据块只读更有利于压缩。X-Engine 引擎云上产品 RDS(X-Engine)曾经充分发挥了 LSM-tree 高压缩低成本特点,同样的数据量,存储空间只占到 RDS(InnoDB)的 1 / 3 甚至更少,RDS(X-Engine)传统的主备架构,仍然面临着主备复制提早大,备份复原慢等问题。基于 LSM-tree 引擎实现一写多读,不仅计算资源和存储资源解耦,多个节点共享一份数据还能进一步压缩存储老本。

基于 LSM-tree 引擎实现一写多读面临着与 B +tree 引擎不一样的技术挑战,首先是存储引擎日志不一样,LSM-tree 引擎是双日志流,须要解决双日志流的物理复制问题;其次是数据组织形式不一样,LSM-tree 引擎采纳分层存储,追加写入新数据,须要解决多个计算节点一致性物理快照以及 Compation 问题。最初,作为数据库引擎,还须要解决一写多读模式下 DDL 的物理复制问题。同时,为了产品化,充分发挥 B +tree 引擎和 LSM-tree 引擎的各自劣势,还面临着新的挑战,即如何在一个数据库产品中同时实现两个存储引擎 (InnoDB,X-Engine) 的一写多读。

三 LSM-tree 引擎一写多读的关键技术

1 PolarDB 整体架构

PolarDB 反对 X -Engine 引擎后,X-Engine 引擎与 InnoDB 引擎依然独立存在。两个引擎各自接管写入申请,数据和日志均存储在底层的分布式存储上,其中 idb 文件示意的是 InnoDB 的数据文件,sst 文件示意的是 X -Engine 的数据文件。这里最次要的点在于 InnoDB 与 XEngine 共享一份 redo 日志,X-Engine 写入时,将 wal 日志嵌入到 InnoDB 的 redo 中,Replica 节点和 Standby 节点在解析 redo 日志后,分发给 InnoDB 引擎和 XEngine 引擎别离回放进行同步。


PolarDB(X-Engine)架构图

X-Engine 引擎架构

X-Engine 引擎采纳 LSM-tree 构造,数据以追加写的形式写入内存,并周期性物化到磁盘上,内存中数据以 memtable 模式存在,包含一个沉闷的 active memtable 和多个动态的 immutable。磁盘上数据分层存储,总共包含 3 层,L0,L1 和 L2,每一层数据按块有序组织。X-Engine 最小空间调配单位是一个 extent,默认是 2M,每个 extent 蕴含若干个 block,默认是 16k。数据记录紧凑存储在 block 中,因为追加写特点,磁盘上的数据块都是只读的,因而 X -Engine 引擎能够默认对 block 进行压缩,另外 block 中的记录还会进行前缀编码,综合这些使得 X -Engine 的存储空间绝对于 InnoDB 引擎只有 1 /3,局部场景 (比方图片空间) 甚至能压缩到 1 /7。无利就有弊,追加写带来了写入劣势,对于历史版本数据须要通过 Compaction 工作来进行回收。无关 X -Engine 的核心技术能够参考发表在 Sigmod19 的论文,《X-Engine: An Optimized Storage Engine for Large-scale E-commerce Transaction Processing》


X-Engine 整体架构

2 物理复制架构

物理复制的外围是通过引擎本身的日志来实现复制,防止写额定的日志带来的老本和性能损失。MySQL 原生的复制架构是通过 binlog 日志进行复制,写事务须要同时写引擎日志和 binlog 日志,这带来的问题是一方面单个事务在要害写门路上须要写两份日志,写性能受制于二阶段提交和 binlog 的串行写入,另一方面 binlog 复制是逻辑复制,复制提早问题也使得复制架构的高可用,以及只读库的读服务能力大打折扣,尤其是在做 DDL 操作时,这个提早会进一步放大。

在 InnoDB 中有 redo 和 undo 两种日志,undo 日志能够了解为一种非凡的“data”,所以实际上 InnoDB 的所有操作都能通过 redo 日志来保障持久性。因而,在进行复制时,只须要在主从节点复制 redo 日志即可。X-Engine 引擎蕴含两种日志,一种是 wal 日志(WriteAheadLog),用于记录前台的事务的操作;另一种是 Slog(StorageLog),用于记录 LSM-tree 形态变动的操作,次要指 Compaction/Flush 等。wal 日志保障了前台事务的原子性和持久性,Slog 则保障了 X -Engine 外部 LSM-tree 形态变动的原子性和持久性,这两个日志缺一不可,都须要复制同步。

共享存储下的物理复制


Primary-Replica 物理复制架构

LSM-tree 引擎一写多读的能力是对 PolarDB 进行性能加强,体现在架构层面就是充分利用已有的复制链路,包含 Primary->Replica 传递日志信息链路和 Replica->Primary 传递协同管制信息链路。InnoDB 事务由若干个 mtr(Mini-Transaction)组成,写入 redo 日志的最小单位是 mtr。咱们在 Innodb 的 redo 日志新增一种日志类型用于示意 X -Engine 日志,将 X -Engine 的事务内容作为一个 mtr 事务写入到 redo 日志中,这样 Innodb 的 redo 和 X -Engine 的 wal 日志能共享一条复制链路。因为 Primary 和 Replica 共享一份日志和数据,Dump_thread 只须要传递位点信息,由 Replica 依据位点信息去读 redo 日志。Replica 解析日志,依据日志类型来散发日志给不同的回放引擎,这种架构使得所有复制框架与之前的复制保持一致,只须要新增解析、散发 X -Engine 日志逻辑,新增 X -Engine 的回放引擎,充沛与 InnoDB 引擎解耦。

因为 LSM-tree 追加写特点,内存 memtable 中数据会周期性的 Flush 到磁盘,为了保障 Primary 和 Replica 读到一致性物理视图,Primary 和 Replica 须要同步 SwitchMemtable,须要新增一条 SwitchMemtable 管制日志来协调。redo 日志长久化后,Primary 通过日志形式将位点信息被动推送给 Replica,以便 Replica 及时回放最新的日志,缩小同步提早。对于 Slog 日志,既能够采纳相似于 redo 的日志形式来被动“push”形式来同步位点,也能够采纳 Replica 被动“pull”的形式来同步。SLog 是后盾日志,绝对于前台事务回放实时性要求不高,不必要将 redo 位点和 SLog 位点都放在一条复制链路减少复杂性,所以采纳了 Replica 的“pull”的形式来同步 SLog。

灾备集群间的物理复制


Primary-Standby 物理复制架构

与共享集群复制不同,灾备集群有独立一份存储,Primary—>Standby 须要传递残缺的 redo 日志。Stanby 与 Replica 区别在于日志起源不同,Replica 从共享存储上获取日志,Standy 从复制链路获取日志,其它解析和回放门路是一样的。是否将 Slog 日志作为 redo 日志一部分传递给 Standby 是一个问题,Slog 日志由 Flush/Compaction 动作产生,记录的是 LSM-tree 形态的物理变化。如果也通过 redo 日志链路同步给 Standby,会带来一些复杂性,一方面是 X -Engine 外部写日志的形式须要改变,须要新增新增文件操作相干的物理日志来确保主从物理构造统一,故障复原的逻辑也须要适配;另一方面,Slog 作为后台任务的操作日志,意味着复制链路上的所有角色都须要同构;如果放弃同构,那么 Standy 节点可能会触发 Flush/Compaction 工作写日志,这与物理复制中,只容许 Primary 写日志是相违反的。实际上,Slog 同步写入到 redo log 中不是必须的,因为 Slog 是后盾日志,这个动作不及时回放并不影响数据视图的正确性,因而,复制链路上只蕴含 redo 日志(X-Engine wal 日志和 InnoDB redo 日志),Standby 本人管制 Flush/Compaction 产生 Slog 日志,这样 Standby 也不用与 Primary 节点物理同构,整个架构与现有体系相匹配,同时也更加灵便。

3 并行物理复制减速

X-Engine 的事务包含两个阶段,第一个阶段是读写阶段,这个阶段事务操作数据会缓存在事务上下文中,第二阶段是提交阶段,将操作数据写入到 redo 日志长久化,随后写到 memtable 中供读操作拜访。对于 Standby/Replica 节点而言,回放过程与 Primary 节点相似,从 redo 中解析到事务日志,而后将事务回放到 memtable 中。事务之间不存在抵触,通过 Sequence 版本号来确定可见性。并行回放的粒度是事务,须要解决的一个关键问题就是可见性问题。事务串行回放时,Sequence 版本号都是间断递增的,事务可见性不存在问题。在并行回放场景下,咱们依然须要保序,通过引入“滑动窗口”机制,只有间断一段没有空洞的 Sequence 能力推动全局的 Sequence 版本号,这个全局 Sequence 用于读操作获取快照。


并行复制框架

一写多读架构下,为了保障同一数据库实例的 Primary、Replica、Standby 三个角色的内存镜像完全一致,新增了一种 SwitchMemtableLog,该 Log Record 在 RW 的 switch_memtable 过程中产生,因而 RO、Standby 不再被动触发 switch_memtable 操作,而是通过从 RW 上同步 SwitchMemtableLog 进行 switch_memtable。SwitchMemtable 操作是一个全局的屏障点,以避免以后可写 memtable 在插入过程中 switch 从而导致数据错乱。另外,对于 2PC 事务,并发管制也须要做适配。一个 2PC 事务除了数据自身的日志,还包含 BeginPrepare、EndPrepare、Commit、Rollback 四类日志,写入过程中保障 BeginPrepare 和 EndPrepare 写入到一个 WriteBatch 中并程序落盘,因而能够保障同一个事务的 Prepare 日志都会被解析到一个 ReplayTask 中。在并行回放过程中,因为无奈保障 Commit 或 Rollback 日志肯定后于 Prepare 日志被回放,因而如果 Commit、Rollback 日志先于 Prepare 日志被回放,那么在全局的 recovered_transaction_map 中插入一个 key 对 xid 的空事务,对应的事务状态为 Commit 或 Rollback;随后 Prepare 日志实现回放时,如果发现 recovered_transaction_map 中曾经存在对应的事务,那么能够依据事务的状态来决定间接提交事务还是抛弃事务。

对于 B +Tree 的物理复制,LSM-tree 的物理复制并不是真正的“物理”复制。因为 B +Tree 传递的 redo 的内容是数据页面的批改,而 LSM-tree 传递的 redo 内容是 KeyValue 值。这带来的后果是,B+tree 物理复制能够基于数据页粒度做并发回放,而 LSM-tree 的物理复制是基于事务粒度的并发回放。B+tree 并发回放有它本身的复杂性,比方须要解决零碎页回放与一般数据页回放先后顺序问题,并且还须要解决同一个 mtr 中多个数据页并发回放可能导致的物理视图不统一问题。LSM-tree 须要解决多个节点在同样地位 SwitchMemtable,以及 2PC 事务回放等问题。

4 MVCC(多版本并发管制)

物理复制技术解决了数据同步的问题,为存储计算拆散打下了根底。为了实现弹性,动静升降配,增删只读节点的能力,须要只读节点具备一致性读的能力,另外 RW 节点和 RO 节点共享一份数据,历史版本回收也是必须要思考的问题。

一致性读

X-Engine 提供快照读的能力,通过多版本机制来实现读写不互斥成果。从上述的 X -Engine 架构图能够看到,X-Engine 的数据实际上包含了内存和磁盘两局部,不同于 InnoDB 引擎内存中 page 是磁盘上 page 的缓存,X-Engine 中内存数据与磁盘数据齐全异构,一份“快照”须要对应的是内存 + 磁盘数据。X-Engine 采纳追加写形式,新数据进来会产生新的 memtable,后台任务做 flush/compaction 工作也会产生新的 extent。那么如何获取一致性视图呢?X-Engine 外部实际上是通过 MetaSnapshot+Snapshot 来治理,首先每个 MetaSnapshot 对应一组 memtable 和 L0,L1, L2 的 extents,这样在物理上确定了数据范畴,而后通过 Snapshot 来解决行级版本的可见性,这里的 Snapshot 实际上就是一个事务提交序列号 Sequence。不同于 InnoDB 事务编号采纳开始序,须要通过沉闷事务视图来判断记录的可见性;X-Engine 事务采纳提交序,每条记录有一个惟一递增序列号 Sequence,判断行级版本的可见性只须要比拟 Sequence 即可。在一写多读的模式下,Replica 节点与 Primary 节点共享一份磁盘数据,而磁盘数据是有内存中数据定期 dump 进去的,因而须要保障 Primary 和 Replica 节点有雷同的切 memtable 位点,从而保证数据视图的一致性。

一写多读下的 Compaction

在一写多读场景下,Replica 能够通过相似于 Primary 的快照机制来实现快照读,须要解决的问题是历史版本回收问题。历史版本的回收,依赖于 Compaction 工作来实现,这里的回收包含两局部,一部分 MetaSnapshot 的回收,次要确认哪些 memtable 和 extents 能够被物理回收掉,另一部分是行级多版本回收,这里次要是确认哪些历史版本行能够被回收掉。对于 MetaSnapshot 的回收,Primary 会收集所有 Replica 节点上的最小不再应用的 MetaSnapshot 版本号,X-Engine 引擎的每个索引都是一个 LSM-tree,因而汇报 MetaSnaphot 版本号是索引粒度的。Primary 收集完 MetaSnapshot 版本号,计算最小能够回收的 MetaSnapshot 进行资源回收操作,回收操作以 Slog 日志的形式同步给 Replica 节点。Replica 节点在回放日志进行资源回收时,须要将内存和磁盘资源离开,内存资源在各个节点是独立的,磁盘资源是共享的,因而 Replica 节点的内存资源能够独立开释,而磁盘资源则对立由 Primary 节点来开释。对于行级多版本的回收,同样须要由 Primary 节点收集所有 Replica 节点最小序列号 Sequence,由 Primary 节点通过 Compaction 工作来打消。这块汇报链路复用 PolarDB 的 ACK 链路,只是新增了 X -Engine 的汇报信息。

5 DDL 的物理复制如何实现

物理复制绝对于逻辑复制一个要害劣势在于 DDL,对于 DDL 而言,逻辑复制能够简略了解为复制 SQL 语句,DDL 在从库上会从新再执行一遍。逻辑复制对于比拟重的 DDL 操作,比方 Alter table 影响十分大,一个 Alter 变更操作在主库执行须要半小时,那么复制到从库也须要再执行半小时,那么主从提早最大可能就会是 1 个小时,这个提早对只读库提供读服务产生重大影响。

Server 层复制

DDL 操作同时波及到 Server 层和引擎层,包含字典,缓存以及数据。最根底的 DDL 操作,比方

Create/Drop 操作,在一写多读架构下,要思考数据与数据字典,数据与字典缓存一致性等问题。一写多读的根底是物理复制,物理复制日志只在引擎层流动,不波及到 Server 层,因而须要新增日志来解决 DDL 操作导致的不统一问题。咱们新增了 meta 信息变更的日志,并作为 redo 日志的一部分同步给从节点,这个 meta 信息变更日志次要包含两局部内容,一个是字典同步,次要是同步 MDL 锁,确保 Primary/Replica 节点字典统一;另一个是字典缓存同步,Replica 上的内存是独立的,Server 层缓存的字典信息也须要更新,因而要新增日志来解决,比方 Drop Table/Drop db/Upate function/Upate precedure 等操作。另外,还须要同步生效 Replica 的 QueryCache,防止应用谬误的查问缓存。

引擎层复制

X-Engine 引擎与 InnoDB 引擎一样是索引组织表,在 X -Engine 外部,每个索引都是一个 LSM-tree 构造,外部称为 Subtable,所有写入都是在 Subtable 中进行,Subtable 的生命周期与 DDL 操作严密相干。用户发动建表动作会产生 Subtable,这个是物理 LSM-tree 构造的载体,而后能力有后续的 DML 操作;同样的,用户发动删表动作后,所有这个 Subtable 的 DML 操作都应该执行结束。Create/Drop Table 操作波及到索引构造的产生和沦亡,会同时产生 redo 管制日志和 SLog 日志,在回放时,须要解决 redo 管制日志和 SLog 日志回放的时序问题。这里咱们将对应 Subtable 的 redo 日志的 LSN 位点长久化到 SLog 中,作为一个同步位点,Replica 回放时,两个回放链路做协调即可,redo 日志记录的是前台操作,Slog 记录的是后盾操作,因而两个链路做协同时,须要尽量避免 redo 复制链路期待 Slog 复制链路。比方,对于 Create 操作,回放 Slog 时,须要期待对应的 redo 日志的 LSN 位点回放结束才推动;对于 DROP 操作,回放 SLog 也须要协同期待,防止回放前台事务找不到 Subtable。

OnlineDDL 复制技术

对于 Alter Table 操作,X-Engine 实现了一套 OnlineDDL 机制,具体实现原理能够参考内核月报。在一写多读架构下,X-Engine 引擎在解决这类 Alter 操作时采纳了物理复制,实际上对于 Replica 而言,因为是同一份数据,并不需要从新生成物理 extent,只须要同步元信息即可。对于 Standby 节点,须要通过物理 extent 复制来从新构建索引。DDL 复制时,实际上蕴含了基线和增量局部。DDL 复制充分利用了 X -Engine 的分层存储以及 LSM-tree 构造追加写特点,在获取快照后,利用快照间接构建 L2 作为基线数据,这部分数据以 extent 块复制模式,通过 redo 通道传递给 Standby,而增量数据则与一般的 DML 事务一样,所以整个操作都是通过物理复制进行,大大提高了复制效率。这里须要限度的仅仅是在 Alter 操作过程中,禁止做到 L2 的 compaction 即可。整个 OnlineDDL 过程与 InnoDB 的 OnlineDDL 流程相似,也是包含 3 个阶段,prepare 阶段,build 阶段和 commit 阶段,其中 prepare 阶段须要获取快照,commit 阶段元数据失效,须要通过 MDL 锁来确保字典统一。与基于 B +tree 的 OnlineDDL 复制相比,基线局部,B+tree 索引复制的是物理页,而 LSM-tree 复制的是物理 extent;增量局部 B +tree 索引是通过记增量日志,回放增量日志到数据页写 redo 日志进行同步,LSM-tree 则是通过 DML 前台操作写 redo 的形式同步。


OnlineDDL 复制

6 双引擎技术

Checkpoint 位点推动

通过 wal-in-redo 技术,咱们将 X -Engine 的 wal 日志嵌入到了 InnoDB 的 redo 中,首先要解决的一个问题就是 redo 日志的回收问题。日志回收首先波及到一个位点问题,交融进 redo 日志后,X-Engine 外部将 RecoveryPoint 定义为 <lsn, Sequence>,lsn 示意 redo 日志的位点,Sequence 为对应的 X -Engine 的事务的版本号。Redo 日志回收与 Checkpoint(检查点)强相干,确保 Checkpoint 位点及时推动是须要思考的问题,否则 redo 日志的沉积一方面影响磁盘空间,另一方面也影响复原速度。这里有一个根本的准则是,Checkpoint=min(innodb-ckpt-lsn, xengine-ckpt-lsn),xengine-ckpt-lsn 就是来源于 X -Engine 的 RecoveryPoint,确保任何引擎有内存数据没有落盘时,对应的 redo 日志不能被清理。为了防止 X -Engine 的 checkpoint 推动影响整体位点推动,外部会确保 xengine-ckpt-lsn 与全局的 redo-lsn 放弃肯定的阀值,超过阀值则会强制将 memtable 落盘,推动检查点。

数据字典与 DDL

X-Engine 作为一个数据库引擎有本人独立的字典,InnoDB 也有本人的字典,两份字典在一个零碎外面必定会存在问题。为了解决问题,这里有两种思路,一是 X -Engine 依然保留本人的数据字典,在做 DDL 时,通过 2PC 事务来保障一致性,这带来的问题是须要有协调者。个别状况下,MySQL 的协调者是 binlog 日志,在 binlog 敞开时是 tclog 日志。显然,从性能和性能角度,咱们都不会强依赖 binlog 日志。咱们采纳了另外一种思路,X-Engine 不再用本身引擎存储元数据,所有元数据均通过 InnoDB 引擎长久化,X-Engine 元数据实际上是 InnoDB 字典的一份缓存,那么在做 DDL 变更时,元数据局部实际上只波及 InnoDB 引擎,通过事务能保障 DDL 的原子性。

通过元数据归一化咱们解决了元数据的原子性问题,但 X -Engine 数据和 InnoDB 元数据如何保障统一也是个问题。比方一个 DDL 操作,alter table xxx engine = xengine,这个 DDL 是将 innodb 表转为 xengine 表,因为表构造变更是 Innodb 字典批改,数据是在批改 X -Engine,是一个跨引擎事务,跨引擎事务须要通过协调者保障一致性。为了防止引入 binlog 作为协调者依赖,tclog 作为协调者没有通过大规模生产环境验证,咱们抉择了另外一种解决形式,具体来说,在波及跨引擎事务时,优先提交 X -Engine 事务,而后再提交 InnoDB 事务。对于 DDL 来说,就是“先数据,后元数据”,元数据提交了,才真正示意这个 DDL 实现。如果中途失败,则联合“提早删除”的机制,来保障垃圾数据能被最终清理掉,通过一个后台任务来周期性的比照 X -Engine 数据与 InnoDB 的字典,以 InnoDB 字典为准,联合 X -Engine 内存元信息,确认这部分数据是否有用。

CrashRecovery

X-Engine 与 InnoDB 引擎一样是 MySQL 的一个插件,X-Enigne 作为一个可选的插件,启动程序在 Innodb 之后。每个引擎在复原阶段都须要通过 redo 日志来将数据库复原到宕机前状态。在双引擎状态下,所有 redo 都在 InnoDB 中,那意味着无论是 InnoDB 引擎还是 X -Engine 引擎在读取日志复原时,都须要扫描整个 redo 日志,相当于整个复原阶段扫描了两遍 redo,这可能使得整个宕机复原过程十分长,升高了零碎的可用性。为了解决这个问题,咱们将 X -Engine 的复原阶段细分,并且调整引擎的启动程序,在 InnoDB 启动前,先实现 X -Engine 的初始化以及 Slog 等复原过程,处于复原 redo 的状态。在 InnoDB 启动时,依据类型将日志散发 X -Engine 引擎,整个流程与失常同步 redo 日志的过程统一。当 redo 日志散发结束,相当于 InnoDB 引擎和 X -Engine 引擎本身的宕机复原过程曾经实现,而后走失常 XA-Recovery 和 Post-Recovery 阶段即可,这个流程与之前保持一致。

HA

PolarDB 反对双引擎后,整个升降级流程中都会嵌套有 X -Engine 引擎的逻辑,比方在 Standby 降级为 RW 前,须要确保 X -Engine 的回放流水线实现,并将未决的事务保存起来,以便后续通过 XA_Recovery 持续推动。RW 降级为 Standby 的时候须要期待 X -Engine 写流水线回放,同时如果还残留有未决事务,须要在切换过程中将这部分未决事务遍历进去存入 Recovered_transactions_汇合供后续并发回放应用。

四 LSM-tree VS B+tree

上节咱们详细描述了基于 LSM-tree 架构的存储引擎,实现一写多读所须要的关键技术,并联合 PolarDB 双引擎介绍了一些工程实现。当初咱们跳进去看看基于 B +tree 和基于 LSM-tree 两种数据组织构造在实现技术上的比照。首先要回到一个基本点,B+tree 是原地更新,而 LSM-tree 是追加写,这带来的区别就是 B +tree 的数据视图在内存和外存一个缓存映射关系,而 LSM-tree 是一个叠加的关系。因此须要面对的技术问题也不同,B+tree 须要刷脏,须要有 double-write(在 PolarFS 反对 16k 原子写后,打消了这个限度);LSM-tree 须要 Compaction 来回收历史版本。在一写多读的模式下面临的问题也不一样,比方,B+tree 引擎复制是单 redo 日志流,LSM-tree 引擎是双日志流;B+tree 在解决并行回放时,能够做到更细粒度的页级并发,然而须要解决 SMO(SplitMergeOperation)问题,防止读节点读到“过来页”或是“将来页”。而 LSM-tree 是事务级别的并发,为了保障 RW 和 RO 节点“内存 + 磁盘”的一致性视图,须要 RW 和 RO 在雷同的位点做 Switch Memtable。下表以 InnoDB 引擎和 X -Engine 引擎为例,列出了一些要害的区别点。

五 LSM-tree 引擎业内倒退情况

目前业内 LSM-tree 类型引擎比拟热的是 Rocksdb,它的次要利用场景是作为一个 KeyValue 引擎应用。Facebook 将 Rocksdb 引擎引入到了他们的 MySQL8.0 分支,相似于 X -Engine 之于 AliSQL,次要服务于他们的用户数据库 UDB 业务,存储用户数据和音讯数据,采纳的依然是基于 binlog 的主备复制构造,目前没有看到有做存储计算拆散,以及一写多读的事件。另外,github 上有一个 rocksdb-cloud 我的项目,将 rocksdb 作为底座,架在 AWS 等云服务上提供 NoSQL 接口服务,相当于做了存储计算拆散,但并不反对物理复制和一写多读。在数据库畛域,阿里巴巴的 Oceanbase 和谷歌的 Spanner 的底层存储引擎都是基于 LSM-tree 构造,这显示了 LSM-tree 作为数据库引擎的可行性,这两个数据库都是基于 Share-Nothing 的架构。基于 Share-Storage 的数据库,到目前为止还没有成熟的产品,PolarDB(X-Engine)是业内第一个基于 LSM-tree 构造的实现的一写多读计划,对于后来者有很好的借鉴意义,LSM-tree 这种构造人造将内存和磁盘存储拆散,咱们充分利用了磁盘存储只读的特点,通过压缩将其老本劣势施展进去,联合一写多读的能力,将老本劣势施展到极致。

六 性能测试

基于 X -Engine 引擎实现一写多读能力后,咱们采纳基准测试工具 sysbench 对性能做了摸底,次要比照了 RDS(X-Engine),PolarDB(X-Engine)以及 PolarDB(InnoDB)的性能。

1 测试环境

测试的 client 和数据库 server 均从阿里云官网购买。client 采纳 ecs,规格是 ecs.c7.8xlarge(32core,64G),测试 sysbench 版本是 sysbench-1.0.20,测试的数据库 server 包含 RDS(X-Engine),PolarDB(X-Engine),PolarDB(InnoDB)均采纳 8core32G 规格,配置文件采纳线上默认的配置。测试场景笼罩了全内存态和 IO-bound 的几种典型的 workload。测试表数目是 250 张表,全内存态单表行数为 25000 行,IO-bound 的表行数为 300 万行。

2 测试后果

RDS VS PolarDB

下面左图是小表全内存场景,右图是大表 io-bound 场景。PolarDB(X-Engine)相比 RDS(X-Engine)次要是写入门路产生了变动,最外围的区别是 RDS 主备架构依赖 binlog 做复制,而 PolarDB 状态只须要 redo 日志即可。PolarDB 状态的写相干 workload 的性能相比 RDS 状态,无论在全内存态,还是 IO-bound 场景,都有很大的性能晋升。

B+tree VS LSM-tree

上图是小表全内存场景,下图是大表 io-bound 场景。PolarDB 状态下,X-Engine 引擎绝对于 InnoDB 引擎还有差距,这个差距次要来源于 range 查问,另外更新场景导致的多版本,也会导致更新时须要做 range 查问,这些因素导致了读写相干的 workload,InnoDB 引擎比 X -Engine 体现更优良。同时咱们能够看到,在 IO-bound 场景,X-Engine 引擎写入更有劣势。

七 将来瞻望

PolarDB(X-Engine)解决方案很好解决了用户的归档存储问题,但目前来看还不够彻底。第一,技术上尽管 PolarDB 反对了双引擎,但咱们还没有充沛将两个引擎联合起来。一个可行的思路是在线归档一体化,用户的在线数据采纳默认的引擎 InnoDB,通过设定肯定的规定,PolarDB 外部主动将局部历史数据进行归档并转换为 X -Engine 引擎存储,整个过程对用户通明。第二,目前的存储都落在 PolarDB 的高性能存储 PolarStore 上,为了进一步降低成本,X-Engine 引擎能够将局部冷数据存储在 OSS 上,这个对于分层存储是十分敌对和天然的。实际上,基于 LSM-tree 的存储引擎有很强的可塑性,咱们目前的工作只是充分发挥了存储劣势,将来还能够对内存中数据结构进行进一步摸索,比方做内存数据库等都是能够摸索的方向。

原文链接
本文为阿里云原创内容,未经容许不得转载。

正文完
 0