摘要
本文介绍了MySQL数据库在国产化ARM环境中出现的第一个大坑——从库复制延迟。作者首先分析了导致这一现象的原因,包括主库的binlog dump线程、从库的IO线程、从库的SQL线程及协调线程等各个方面的因素。然后,作者进行了详细的调试和分析,发现了社区版MySQL在ARM架构下存在的获取CPU缓存行大小函数兼容性BUG。最后,作者提出了解决方案并在国产ARM架构中使用TXSQL避免了这个问题。
踩坑背景
在信创背景和国产化浪潮下,监管机构给金融数据库的安全性和可靠性提出了更高的要求。为了满足监管要求,我们尝试在几个支付业务场景把MySQL数据库部署在国产化ARM服务器上。
业务刚上线时,数据库运行比较平稳。一段时间过后,我们就踩到了数据库在国产化架构下的首个大坑。
我发现在业务高峰期MySQL从库会出现较大的复制延迟。触发场景: 业务高峰期18:00前后,insert的QPS达到1.7k/s,update的QPS达到1.7k/s ,即TPS达到3.4k/s之后,ARM架构的从库就会出现复制延迟,而X86架构下的从库复制正常。
从原理入手
MySQL从库的复制延迟问题,实际上是数据库领域一个常见问题。要分析复制延迟问题,我们首先回顾一下主从复制的基本原理。
- 主库的binlog dump线程负责发送binlog给从库
当 slave 节点连接 master 节点时,master 节点会创建一个 binlog dump 线程,用于发送 binlog 的事件。
- 从库的I/O线程负责接收binlog,并写入relay log
当 slave 节点启动复制后,slave 节点会创建一个I/O线程用来连接 master 节点,请求 master 节点中的 binlog 事件;I/O线程接收 master 节点发来的事件,保存在本地 relay log 中。
- 从库的SQL线程负责读取relay log,并回放事务
SQL线程负责读取 relay log 中的事件,解析成具体的SQL并执行,最终保证主从数据的一致性。
- 从库并行复制模式下,还有个Coordinator线程负责读取relay log分发给SQL线程
在并行复制的模式下,有个单独的 Coordinator 线程专门负责读取relay log日志,并将读取到的事务分发给多个SQL线程来处理。
根据以上主从复制的基本原理,复制延迟可能发生于多个线程:
- 主库 binlog dump 线程发送 binlog事件慢;
- 从库IO线程接收binlog事件并写入relay log慢;
- 从库协调线程读取relay log并分发给SQL线程慢;
- 从库SQL线程执行事务慢。
一般情况下,读取和写入日志都是顺序读写,速度较快;最容易导致复制延迟的是SQL线程慢,从库性能差、事务过大等多种情况都可能引发SQL线程回放事务慢,进而导致复制延迟。
山重水复疑无路
本着大胆猜想,小心验证的理念,根据以上主从复制延迟的基本原理,我首先检查了从库复制状态,relay log的更新与上游主库的binlog基本同步,可以排除binlog dump线程和IO线程的影响。剩下的协调线程和SQL线程中,最有可能出现问题的就是SQL线程了,先排查SQL线程的问题。
SQL线程
SQL线程回放事务的效率主要跟大事务、从库IO性能、并发度等因素有关。
- 大事务
这里业务写入数据库的模型很简单,可以首先排除大事务的可能。
- 从库IO性能
从ARM服务器的IO性能监控来看,单块磁盘的IO使用率很高达到了100%,不过等待和队列参数并没明显异常;另外MySQL写日志的线程CPU使用率也很高。复制延迟,可能跟从库IO性能有关。
于是,我尝试把两块nvme盘做个条带化的LVM,提升IOPS能力,然后把数据迁移到LVM,结果:SQL线程回放速度没有明显变化。
接着,我尝试关闭从库写日志双1设置,降低回放对IO能力的依赖,结果:SQL线程回放速度也没有明显变化。
结论:复制延迟跟从库IO性能无关。
- 并发度
是否SQL线程并发度不够导致复制延迟?
这个很好验证了,我将并发线程依次调整到6个、8个、16个、24个,结果:从库回放速度几乎没有变化,一直保持在1.68K/S左右。
- 绑核
由于ARM架构下绑核对性能的提升非常明显,我尝试将SQL线程绑定到指定CPU,同时将从库的内存也一并绑定,结果:SQL线程的回放速度只提升了6%;显然,绑核对性能有一定提升,但并不是导致复制延迟的罪魁祸首。
基于以上分析和验证,基本可以排除SQL线程的影响。
协调线程
剩下可能影响的就是协调线程了。
我重新看了一下复制延迟的现场,发现了一些端倪。ARM架构的从库复制延迟时,看上去SQL回放并行度并不高。虽然从库有8个并行SQL线程数,但其中只有1-2个活跃线程,其他线程处于空闲状态,并且SQL线程间事务ID间隔很大,状态如下:
作为对比,X86架构下的半同步从库的回放并行度明显高于ARM节点。
同时,为了排除主库上事务本身并行度不足的因素,我又分别解析主库和半同步备机的binlog,结果:上下游的binlog中事务看上去都是可以并行的。
那到底是什么导致SQL回放的并行度变低了呢?
很自然的猜想到了,是不是协调线程的负载较高,导致分发事务的效率变低,使得SQL线程大部分都处于空闲状态。
业务高峰期的CPU监控可以看出,ARM架构从库节点的Coordinator协调线程的CPU使用率99.9%,已经用完了一整颗逻辑CPU。而SQL线程的CPU使用率普遍只有40%。
作为对比,X86架构从库节点的协调线程的cpu使用率只有不到40%,SQL线程的CPU使用率在30-50%之间。
难道是ARM架构下的单核CPU处理能力低于X86架构,导致协调线程CPU使用率高,从而无法有效分发事务给SQL线程,阻塞了从库回放?
就CPU处理能力的问题,我咨询了一下TEG服务器相关同学,得到的反馈是,“X86 CPU是4208,8核16线程,在低负载时,单个核可以睿频到3.2G,算力等同于单个物理核;鲲鹏只有物理核,单核主频最高2.6G,所以低负载时鲲鹏核的主频处于弱势。鲲鹏胜在核数多,并行能力会更好些。”
看起来,ARM架构下物理核的单核性能的确是比X86架构要差一些。这里可以得出初步结论:ARM单核处理能力不足(或其他内核原因),导致SQL协调线程的处理效率低,SQL回放并行度不高,引发高并发下ARM架构从库复制延迟。
但是从库的协调线程只能用到一个逻辑核,那这个问题岂不是无解了!!!难道ARM架构下MySQL从库的回放性能极限就是3.4K/S?
柳暗花明又一村
问题始终还是要解决的,毕竟数据库国产化是大势所趋。
既然ARM架构下的协调线程单核使用率高,那么我就来看看导致消耗在哪里。
精细分析
首先,通过火焰图来分析下MySQL的系统调用耗时。以下是ARM架构下MySQL的协调线程CPU系统调用情况,get_slave_worker函数调用中,get_least_occupied_worker函数占比极高。
对比ARM从库和X86从库的火焰图,差异点在于协调线程调用函数get_slave_worker这里。
从上述两张图可以看出,ARM架构下MySQL调用get_least_occupied_worker函数的CPU时间占比远超X86架构下的MySQL,并且几乎没有调用get_free_woker函数。另外,get_least_occupied_worker函数中大部分CPU占用在__sched_yield。
同时,结合pstack打印的协调线程的堆栈信息,可以看到协调线程通过get_least_occupied_worker()函数获取一个最空闲的SQL线程,然后通过往无锁队列Commit_order_queue插入一个元素的方式向Commit_order_manager注册一个新的事务。入队时调用了sched_yield()系统调用。
代码语言:javascript复制Thread 16 (Thread 0xfffb8fe3efe0 (LWP 127910)):
#0 0x0000fffbcb716918 in sched_yield () from /lib64/libc.so.6
#1 0x00000000019bc8a0 in __gthread_yield () at /usr/include/c /7/aarch64-redhat-linux/bits/gthr-default.h:692
#2 yield () at /usr/include/c /7/thread:351
#3 push (to_push=2, this=0xfff330f002e0) at mysql-arm.7.5/sql/containers/integrals_lockfree_queue.h:833
#4 operator<< (to_push=<synthetic pointer>, this=0xfff330f002e0) at mysql-arm.7.5/sql/containers/integrals_lockfree_queue.h:750
#5 cs::apply::Commit_order_queue::push (this=0xfff330f00290, index=2) at mysql-arm.7.5/sql/changestreams/apply/commit_order_queue.cc:172
#6 0x00000000019b419c in Commit_order_manager::register_trx (this=<optimized out>, worker=worker@entry=0xfff33091d000) at mysql-arm.7.5/sql/rpl_replica_commit_order_manager.cc:67
#7 0x00000000019d6a34 in Mts_submode_logical_clock::get_least_occupied_worker (this=0xfff330f900b0, rli=0xfffbca5eb000, ws=<optimized out>, ev=0xfff32afc1e60) at mysql-arm.7.5/sql/rpl_mta_submode.cc:976
#8 0x0000000001916c30 in Log_event::get_slave_worker (this=this@entry=0xfff32afc1e60, rli=rli@entry=0xfffbca5eb000) at mysql-arm.7.5/sql/log_event.cc:2770
#9 0x0000000001917c34 in Log_event::apply_event (this=this@entry=0xfff32afc1e60, rli=rli@entry=0xfffbca5eb000) at mysql-arm.7.5/sql/log_event.cc:3307
#10 0x00000000019990f4 in apply_event_and_update_pos (ptr_ev=0xfffb8fe3d968, ptr_ev@entry=0xfffb8fe3d9b8, thd=thd@entry=0xfff330e53000, rli=rli@entry=0xfffbca5eb000) at mysql-arm.7.5/sql/rpl_replica.cc:4391
#11 0x00000000019a9290 in exec_relay_log_event (thd=0xfff330e53000, rli=rli@entry=0xfffbca5eb000, applier_reader=0xfffb8fe3e3f0, applier_reader@entry=0x0, in=in@entry=0xfff32afc1e60) at mysql-arm.7.5/sql/rpl_replica.cc:4908
#12 0x00000000019aba84 in handle_slave_sql (arg=arg@entry=0xfffbca5dc000) at mysql-arm.7.5/sql/rpl_replica.cc:7083
#13 0x000000000212ab60 in pfs_spawn_thread (arg=0xfff3373d0260) at mysql-arm.7.5/storage/perfschema/pfs.cc:2947
#14 0x0000fffbcbe87c48 in start_thread () from /lib64/libpthread.so.0
#15 0x0000fffbcb72f600 in thread_start () from /lib64/libc.so.6
根据火焰图和前面定位到的协调线程单核CPU使用率高达99.9%,基本可以确认协调线程的大部分时间在执行sched_yield()系统调用。sched_yield()系统调用函数一般用在用户态自旋锁上,用于将当前进程让出CPU,以便其他进程可以获得更多的CPU时间片。锁竞争CPU必然到100%,sched_yield只是自旋锁竞争的一个结果而已。也就是说,协调线程的CPU使用率99.9%是自旋锁的结果,并不是原因。
再结合以上堆栈信息,可以看出ARM架构下的协调线程是在往无锁队列Commit_order_queue插入新的事务时,未满足条件而自愿让渡出CPU。Commit_order_queue应该就是为了保证多线程回放场景下从库事务提交顺序与主库一致而创建的事务提交顺序队列,在参数slave_preserve_commit_order
=1 时生效。该参数主要为了保证主从事务提交顺序一致,打开之后对从库的并行回放速度有很大的影响,无论在X86还是ARM架构下皆是如此。该参数在金融数据库中肯定是打开的,在MySQL 8.0中默认也是打开的。那就很有可能是在commit order模式下,MySQL代码与ARM架构不兼容导致的。
小心验证
首先,在ARM架构从库中尝试关闭slave_preserve_commit_order
参数,从库回放速度瞬间提升了几倍(预期内结果)。同时,将X86架构的从库也关闭该参数,对比结果是ARM从库和X86从库的回放速度基本相当,与猜测一致。
接着,对比了ARM架构的MySQL 5.7 和 8.0.28以及最新的8.0.34版本,在打开slave_preserve_commit_order
参数时,从库回放速度都无法超约3.4K/S,说明这个问题在社区MySQL中普遍存在。
然后,对比验证了TXSQL-8.0.30在ARM架构下该场景的从库回放速度可以明显高于3.4K/S,基本与X86版本一致。很可能TXSQL发现并修复了社区版这个BUG。
于是,我连忙咨询了TXSQL内核同学,得到的反馈却是他们并没有针对ARM架构下复制延迟问题做过优化。TXSQL这条线并没有获得突破。
那就只能去官方社区找找有没有相关的BUG。搜索了社区所有跟ARM架构下复制相关的问题,都没有找到相关BUG。
这就有点匪夷所思了。
明明应该是MySQL在ARM架构下的兼容性BUG,可就是搜不到相关的BUG单;看上去TXSQL明明解决了这个BUG,内核同学却不承认做过这方面优化,在当时看来着实有点难以理解。
峰回路转
一波未平,一波又起,我们在ARM架构下又踩到了另一个从库复制中断的坑,同样看上去应该是MySQL与ARMJ架构的兼容性有关。
在给官方社区提交BUG时,我顺带把社区关于ARM架构的BUG又翻了一遍。这次终于发现了一篇相关的BUG:Parallel replication not working with slave_preserve_commit_order=1 。(看来搜索社区BUG的能力还是有待提高~)
根据BUG描述,ARM架构下的MySQL 8.0的并行回放存在问题,当打开commit_order时,ARM架构的MySQL并行回放同一时间只有1个SQL线程工作。在ARM架构下,当已经有事务回放时,系统调用 sysconf 获取CPU缓存行大小时可能会返回 0,导致 Commit_order_manager 无法将工作线程 ID 推送到 Commit_order_queue 中。不过,从BUG回复来看MySQL官方似乎并不认可存在着这个问题。
虽然官方不太认可这个问题,TXSQL也不承认自己解决了这个问题。但事实上,MySQL的确存在这个问题,并且TXSQL也确实解决了。
那就对比一下TXSQL和MySQL的代码一看便知。
以下为社区版MySQL 8.0.28的相关代码
代码语言:javascript复制aligned_atomic.h:78
<<<
#elif defined(__linux__)
static inline size_t _cache_line_size() {
return sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
}
TXSQL的代码不便贴出来,这里以MySQL 8.0.35的代码作为对比。(是的,你没有看错,MySQL在后来10月25日发布的最新版MySQL 8.0.35中修复了这个BUG,尽管Release Note中描述的是为了解决MySQL意外宕机问题;而TXSQL对此的修复是基于compile fixup~)
代码语言:javascript复制#elif defined(__linux__)
static inline size_t _cache_line_size() {
long size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
if (size == -1) return 64;
// returns 0 on s390x RHEL 7.x and some __arch64__ configurations.
if (size == 0) {
FILE *p = fopen(
"/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size", "r");
if (p) {
if (fscanf(p, "%ld", &size) != 1) size = 0;
fclose(p);
}
}
if (size > 0) return static_cast<size_t>(size);
return 64;
}
这里的修复方式看上去也很简单,当系统调用sysconf获取CPU的cache_line_size返回0时,就从/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
取值。
我根据以上修复代码修改了一下MySQL 8.0.28相关代码,重新在ARM架构下编译之后,同样的业务场景下社区版MySQL 8.0.28的从库回放速度可以提升到5.14k insert 5.14k update= 10.28K/S,相比修复之前速度提升了3倍,与X86从库的回放速度相当。并且,修复后的ARM从库的火焰图与X86从库也基本一致。
至此,ARM架构下社区版MySQL从库复制延迟的大坑总算是踩平了。社区版MySQL在ARM架构下获取CPU缓存行大小的函数兼容性BUG导致commit order场景下事务入队阻塞,导致虽然存在多个SQL线程但同一时间只有一个线程活跃,进而严重限制了SQL回放速度导致高负载下从库复制延迟。
好在我们国产化项目中选择了TXSQL作为主要数据库,不得不说TXSQL版本在国产化ARM架构下的表现比社区版强很多。如果在国产化ARM架构下选用社区版MySQL作为数据库,那就只能考虑最新的8.0.35版本了。