亚马逊AWS官方博客

精益求精 – Amazon Aurora MySQL 在复制性能上的不断提升

1. 前言

Amazon Aurora 是亚马逊云科技自研的云原生关系数据库,它在提供和开源数据库 MySQL、PostgreSQL 的完好兼容性同时,也能够提供和商业数据库媲美的性能和可用性。

Aurora 的性能提升不仅包含应用读写吞吐量的提升,也包含复制延迟的降低。一个 Aurora 集群包含 1 个写节点和多达 15 个读节点。在写节点和读节点之间的数据传输机制上,Aurora 创新性地使用 Redo Log 传输来实现。Aurora 的架构是计算存储分离,写和读节点共享存储,在写节点处理写请求时,会将 Redo Log 传送给存储系统,同时也会传送给读节点。读节点在收到 Redo Log 后,会判断 Redo Log 所修改的数据页是否在自己当前的缓冲区中:如果存在,可以按照一定逻辑将 Redo Log 应用到对应数据页上;如果不存在,可以直接忽略掉这部分 Redo Log,后续需要时可以直接去共享存储中读取数据页。Redo Log 的传输使得数据传输量相比传统的 Binlog 大幅降低,再加上读节点应用 Redo Log 的简洁处理逻辑,使得 Aurora 的复制延迟很低,通常在 20 毫秒以内。相应处理逻辑如下图所示。

由于低复制延迟和读节点自动伸缩的能力,用户可以将非必须要求强一致的应用分散部署到 Aurora 不同节点上。高性能、高可用以及良好的扩展性使得 Aurora 得到用户的喜爱,也一度成为亚马逊云科技用户使用量增幅最快的服务。

因为 Aurora 本身读写节点是通过 Redo Log 复制的,如果单纯使用 Aurora,是不需要开启 Binlog 的。但是,用户有时也需要把数据从 OLTP 数据库中导出,比如去做后续的复杂数据分析等。对于 MySQL 而言,数据导出最通用的方式便是 Binlog。Aurora MySQL 与社区 MySQL 是完好兼容的,所以也支持消费端以 Binlog Consumer 的方式来将数据持续导出。打开 Binlog 以及有 Binlog Consumer 连接都会对 MySQL 带来性能上的影响,所以 Aurora MySQL 在 Binlog 方面不停进行优化,力争减少开启 Binlog 带来的影响。

本篇博客聚焦 Aurora MySQL 在 Binlog 方面的优化历程,首先会介绍下 Binlog 的机制,然后会分享下 Aurora MySQL 的 Yield 机制和 Binlog I/O Cache,最后会重点介绍下 Aurora MySQL 3 推出的 Enhanced Binlog 这一新功能以及对应的性能测试情况。

2. Binlog 机制

Binlog 是 MySQL 主从节点同步数据的最常见方式。在主节点开启 Binlog 后,MySQL 会在事务执行过程中记录数据页变更 Redo Log 的同时,也会记录 Binlog,来描述数据在逻辑意义上的修改。Binlog 最小的单位为Binlog Event,多个 Binlog Event 构成一个 Binlog 文件,一个 Binlog 文件的大小通常是 128MB。对于大事务,Binlog 文件大小也可能会超过 128MB。Binlog 有 Row、Statement 和 Mixed 三种不同的格式,可以决定 Binlog Event 中数据存放的内容,Row 表示真实的数据,Statement 表示 SQL 语句,而 Mixed 是两者的混合,会尽可能采用 Statement 格式来记录 Binlog,在 Statement 方式在某些场景下可能导致主从不一致的情况下(比如获取当前系统时间),会采用 ROW 格式来记录 Binlog。开启 Binlog 本身会需要额外的数据处理和刷盘逻辑,会带来一定的性能损耗。

当有另一个模块需要消费 Binlog 时,它会以 Binlog Consumer 的身份连接到主节点。下图展示了另一个 MySQL 数据库作为 Binlog Consumer 的情况下主从同步机制的示意图。实际应用中也有可能会是其他流数据处理工具比如 Kafka、DMS 连接到 MySQL 主库,MySQL 主库的处理逻辑都是相同的。

每当有一个副本连接到 MySQL 主库时,MySQL 主库中都会有一个 Dump 线程专门用来读取 Binlog Event,并将 Binlog Event 通过网络发送给 Consumer。如果 Binlog Consumer 也是 MySQL 数据库,会有一个专门的 IO 线程来接收主库传输的数据,并将接收到的 Binlog Event 存放在 Relay Log 中。从库 MySQL 上还会有一个或者多个 SQL 线程,来读取 Relay Log,并将读取到的 Binlog Event 进行重放,从而使从库得到与主库一致的数据。

主库上因为前端线程在处理用户写入请求时需要将 Binlog Event 写入到 Binlog 文件,而 Dump 线程需要读取 Binlog 文件,尽管 Binlog 文件通常以 128MB 为单位进行存储,当两者操作同一个 Binlog 文件时,仍然会带来加锁竞争等,所以有 Binlog Consumer 连接到 MySQL 主库时会进一步由于锁冲突额外消耗 MySQL 主库的资源,影响到前端应用程序的返回时延。MySQL 支持多个 Binlog Consumer 同时连接,但每个连接都会有对应的 Dump 线程来读取 Binlog,连接数越多,对主库的性能影响也就越大。

3. Aurora MySQL 的 Yield 和 I/O Cache 机制

Aurora MySQL 兼容社区版 MySQL,在 Binlog 处理逻辑上尤其是前端线程、Dump 线程、IO 线程和 SQL 线程等处理上与社区保持一致,只是将 Binlog 文件的存储由本地磁盘转到了远程的共享存储上。所以开启 Binlog 以及有 Binlog Consumer 连接到 Aurora MySQL 同样会带来性能的损耗。

Aurora MySQL 一直致力于减少由于开启 Binlog 对 Aurora 带来的性能影响。在 Aurora MySQL 1.17.6 和 2.04.5 版本中提出了 Binlog Yield 的机制,Aurora MySQL 2.10 版本中进一步提出了 I/O Cache 的机制来减少 Binlog 竞争冲突。

Binlog Yield 的含义是在 Dump 线程与前端应用对应的线程在操作同一个 Binlog 文件引发冲突时,让 Dump 线程 Yield 等待一段时间,等到等待时间期满或者前端应用线程操作完毕当前 Binlog 文件,再让 Dump 线程继续工作。等待时间由变量 aurora_binlog_replication_max_yield_seconds 控制。Yield 的机制能够在发生冲突时优先执行前端应用线程,能够降低对用户应用的响应延迟,但会一定程度上会带来复制延迟的增加。

Binlog I/O Cache 顾名思义是单独开辟一块内存缓冲区,用来存放 Binlog。前端线程可以将 Binlog Event 写入到内存缓冲区中,Dump 线程可以从内存缓冲区中读取 Binlog。在 Binlog 复制延迟比较低的时候, Dump 线程和前端线程的交互可以在内存中完成,而不再需要去远程存储系统中读取并加锁处理,因此提升了 Binlog 复制传输的性能。

关于 Binlog Yield 和 Binlog I/O Cache 的更详细资料,您可以参阅官方文档博客

4. Aurora MySQL 3 的 Enhanced Binlog

Aurora MySQL 3.03 支持的 Enhanced Binlog 从另一个角度降低了开启 Binlog 带来的性能损耗。它能将打开 binlog 对应用程序的影响降低到 13% (之前可能最高达到 50%),也能提升计算节点的吞吐。此外,Binlog 开启时,数据库故障恢复效率与社区版 binlog 相比提升了 99%。

上面示意图对比展示了 Enhanced Binlog 和 普通 Binlog 在 Aurora MySQL 架构中的不同。Enhanced Binlog 提出以前,如果用户把 Aurora MySQL 引擎开启了 Binlog,Aurora MySQL 写节点在写 Redo Log 的同时,也会把 Binlog 写出到存储中去,这样,在发生写节点 failover 时,新的写节点就能依据共享存储中的信息做好 failover 以及后续持续写入。另外,Binlog 文件的写入是串行完成的。在 Enhanced Binlog 架构图中,Aurora MySQL 将 Binlog 存储在单独的存储引擎中,并更改了计算层和 Binlog 引擎中交互 Binlog 的方式,从串行写封装文件接口的形式改成打散并行写 Binlog Event 的形式,Binlog 引擎可以完成 Binlog Event 的排序和归集。

上图进一步展示了 Aurora MySQL 开启 Enhanced Binlog 之前和之后对应的处理逻辑的对比。传统 Binlog 处理流程是两阶段提交的方式,即 Redo Log Prepare -> Binlog Commit -> Redo Log Commit。Binlog 刷盘的动作是在 Redo Log Prepare 完毕顺序刷到存储层,128MB 的 Binlog 文件刷出是需要经历一段时间的,对于较大的事务,Binlog 文件也可能超过 128MB。当然,Aurora MySQL 2 也对超过 512MB 的大 Binlog 文件做了提前拷贝到存储层的优化,但对于 512MB 以下的 Binlog 文件,传输时需要耗费一定时间的。而 Enhanced Binlog 单独的存储引擎存放 Binlog,可以使得刷 Redo Log 和 Binlog 的操作可以同步进行,在事务提交时,直接就通知两个存储引擎进行单独的 Commit 操作,节省了等待 Binlog 刷盘的时间。

5. Enhanced Binlog 测评

针对 Aurora MySQL Enhanced Binlog,我们也做了一些测试,供读者参考。我们有两套 Aurora MySQL 集群:

  1. Aurora MySQL 3.03.1 版本。R6g.8xlarge 集群,一写一读,打开 Binlog。
  2. Aurora MySQL 3.03.1 版本。R6g.8xlarge 集群,一写一读,打开 Binlog,并打开 Enhanced Binlog。

为打开 Binlog,我们设置参数 binlog_format 为 ROW。为了使用 Sysbench 测试较高并发,我们将 max_prepared_stmt_count 设置成了最大值 1048576。

为打开 Enhanced Binlog,我们设置了三个参数 aurora_enhanced_binlog,binlog_backup,binlog_replication_globaldb,参数取值如下图所示。

我们运行的负载是采用 Sysbench 进行压测,Sysbench 运行在 EC2 上,EC2 机型是 c5.18xlarge。测试过程中 EC2 没有成为瓶颈。

我们测试了两组不同的数据: 1) 16 张表,每张表 1 千万条记录; 2) 250 张表,每张表 25000 条记录。我们测试的 Sysbench 并发请求从 2,4,8,一直增加到 4096。Sysbench 运行命令如下:

[ec2-user@]$ sysbench --version
sysbench 1.1.0-db694e7
[ec2-user@]$ cat longrun.sh 
for each in 2 4 8 16 32 64 128 256 512 1024 2048 4096; do ./enhancedbinlog_writeonly.sh $each run | tee -a enhancedbinlog_writeonly.out; done
[ec2-user@]$ cat enhancedbinlog_writeonly.sh 
sysbench oltp_write_only --db-driver=mysql --mysql-user=admin --mysql-password=…. --mysql-db=sb10m --mysql-host=XX.XX.XX.XX --mysql-port=3306  --tables=16  --table-size=10000000  --rate=0  --report-interval=60 --percentile=99 --histogram=on --rand-type=uniform --forced-shutdown --db-ps-mode=disable --simple_ranges=0 --distinct_ranges=0 --sum_ranges=0 --order_ranges=0 --point_selects=0 --index_updates=1 --threads=$1 --time=1200 --report-interval=5 --thread-init-timeout=120 --mysql-ignore-errors=all run

下面展示了 16 张表每表 1 千万条记录的测试结果。我们可以看到随着并发线程的增多,Enhanced Binlog 的优势愈发明显,在 4096 个并发线程时,Enhanced Binlog 能达到 41%的性能提升。

下面展示了 250 张表每表 25000 条记录的测试结果。我们可以看到和前面类似的结论。随着并发线程的增多,Enhanced Binlog 的优势愈发明显,在 4096 个并发线程时,Enhanced Binlog 能达到 23%的性能提升。

下面表格展示了 Enhanced Binlog 故障恢复的速度。我们可以看到 Enhanced Binlog 能够将故障恢复的速度提升 92%以上。该表格摘自另外一篇博客

此外,我们还针对 ZeroETL 进行了测试,因为 ZeroETL 是依赖于 Aurora Enhanced Binlog 的,测试结果表明,即便开启了 ZeroETL,即有 Binlog Consumer 从 Aurora MySQL 中读取数据,Aurora MySQL 对 OLTP 的性能保持不变。原因在于 ZeroETL 能够从Binlog 存储引擎中直接读取数据,无需再联系 Aurora MySQL 计算节点。

总体而言,单独的 Binlog 存储引擎有几个优势:

1)提升性能:存储层进行 Binlog 排序逻辑,可以增加计算层的并行度,减少加锁,加速事务两阶段提交的速度。
2)加速故障恢复:避免传统 Binlog 故障恢复时必须顺序读取 Binlog 到计算层的操作。可以在保证一致性前提下按需恢复事务。可以将故障恢复时间从几分钟降低到几秒。
3)是实现直接从 Aurora MySQL 存储层取 Binlog 的基础。比如 Aurora MySQL 和 Redshift 之间的 ZeroETL 功能就是基于 Enhanced Binlog 来执行实现的,避免了有 Binlog Consumer 连接到 Aurora MySQL 计算实例对于计算节点资源的进一步竞争和消耗。

6. 总结

本博客介绍了 Aurora MySQL 在复制性能提升上的不断优化和尝试。Aurora MySQL 首发版本中基于 Redo Log 的物理复制机制带来通常在 20 毫秒以下的复制延迟,使很多客户能够将更多的读应用发送到读节点进而达到更好扩展性。

基于用户对 Binlog 的诉求和 MySQL 原生 Binlog 机制带来对性能影响,Aurora MySQL 1.17.6 和 2.04.5 中首先提出了 Binlog Yield 机制通过 Dump 线程让路,给前端线程更高优先级,降低了有 Binlog Consumer 连接时对应用延迟的影响;Aurora MySQL 2.10 中进一步改进,提出了 Binlog I/O Cache ,既能在有 Binlog Consumer 连接时降低对前端应用延迟的影响,又不过度拖累复制延迟;Aurora MySQL 3.03 中更是重塑了架构,将 Binlog 放在了单独的存储引擎上,不仅降低了 Binlog 在写节点落盘的时延,更是创造性地使 Binlog Consumer 能从存储引擎上读取 Binlog 信息,无需再从 Aurora MySQL 主节点上进行将 Binlog 文件从存储层读出再发送,彻底避免了 Binlog Consumer 和前端应用线程的竞争。所以不论是否有 Binlog Consumer 的连接,Aurora MySQL 的性能不受影响。

我们建议您有 Aurora MySQL 数据做后续分析的场景时,可以评估 Enhanced Binlog,并保持持续关注。

本篇作者

马丽丽

亚马逊云科技数据库解决方案架构师,十余年数据库行业经验,先后涉猎 NoSQL 数据库 Hadoop/Hive、企业级数据库 DB2、分布式数仓 Greenplum/Apache HAWQ 以及亚马逊云原生数据库的开发和研究。