PXC 升到 8.0 后每 30 秒一阵巨量磁盘写:Linux 大 folio 遇上 mmap 的 Galera cache
一个 PXC 从 5.7 升级到 8.0 的客户案例:升级后工作负载没变、MySQL 参数没变,但每个节点都会周期性爆发大量磁盘写。MySQL 侧的工作负载统计完全解释不了这个写入量。
现象与初步排查
pidstat / iostat 确认写盘只来自 mysqld,责任线程是 Galera 接收线程(galera_recv)、写节点上的连接线程、以及从节点上的 Galera applier 线程。InnoDB 线程(redo、undo、表空间)和 binlog 的写入量与 Galera 线程差距巨大。爆发期间有几 GB 的 write-set 被接收和应用,但 InnoDB 的 page 变化很小。
几秒爆发窗口内按线程拆分:
| 线程 | 写入量 | 占比 |
|---|---|---|
| applier(多线程) | 13.0 GB | 61% |
galera_recv-0 | 5.2 GB | 24% |
ib_pg_flush* | 2.0 GB | 9% |
ib_log_writer | 0.76 GB | 4% |
Galera_srvc | 0.29 GB | 1% |
第一个怀疑是 binlog_row_image=full:大 blob/text/json 列会把 write-set 撑大。但 binlogsum 统计出来的量平缓,没有对应波动,排除。
真因:操作系统也一起升级了
关键补充信息:这次不只升了 PXC,操作系统也从 Debian 11(内核 5.10)升到 Debian 12(内核 6.1)。而 Galera 插件处理 cache 走的是 mmap() 路径。
内核侧的变化时间线:
- 5.16 引入 page folios;
- 5.18 XFS 开始支持 page cache 的 large folios,这个集群的 Galera cache 正好放在 XFS 上;
- 早期实现(5.18–6.5)里
write()/pwrite()路径也受影响,但 InnoDB 表空间通常用 O_DIRECT,redo/binlog 是小块追加,只会形成 order-0(4kB)folio; - 6.6 加入 per-block dirty tracking:iomap 记录重叠的文件系统块(fs-block 粒度)。但这条优化只对
write()/ iomap 路径生效。mmap写入只能按整个 folio 跟踪,一个 4kB 块的更新会让写回把整个 folio(最大 2MB)刷下去。
readahead 大小由 optimal_io_size 推导到每个块设备的 read_ahead_kb,直接影响 large folio 的尺寸(128kB 起,可到 2MB)。爆发之所以周期性出现,是 writeback 机制决定的:内核每 dirty_writeback_centisecs(默认 5s)检查一次脏数据年龄,与 dirty_expire_centisecs(默认 30s)比较,现场观察到的 burst 间隔约 33 秒,与这对参数吻合。
放大倍数还取决于工作负载形态:写集中在同一个 folio 内,放大很轻;随机更新散落在许多 folio 上,放大就非常可观。
为什么偏偏是 Galera cache
- gcache 文件从出生就是
mmap的;默认 128MB,为了覆盖 IST 通常配得更大; - 除了顺序 append 新 write-set,还有大量小的随机更新——事务应用完成后把 status 改成
RELEASED,等待被覆盖; - MySQL 数据目录和
galera.cache常放在 RAID 阵列这类大存储上;软件 RAID 会按阵列几何和上报的 optimal I/O 尺寸成比例调大 readahead,folio 因此更大(客户现场就是这种情况); - 节点启动时 recovery 默认开启,会顺序扫描整个 gcache 文件,把大片 page cache 预热成大 folio。
任何 PXC/Galera 集群,只要内核和文件系统支持 large folios 就可能中招。默认 128kB readahead 时影响可能不易察觉;readahead 调大、folio 到 2MB 后,放大就藏不住了。
Workaround
以下方案对任何使用 mmap 文件的应用都适用:
- 换用 EXT4:它的 folio 支持比 XFS 晚得多(XFS 5.18,EXT4 6.16),在本案例中有效;
- 调小块设备的 readahead(代价是可能损失一些性能);
- 修改应用的
mmap调用,用madvise()传MADV_RANDOM提示,强制随机访问从而关掉 readahead; - 在 Linux 6.6+ 上放弃
mmap()路径,改用pwrite(); - 用定制内核编译时去掉
CONFIG_TRANSPARENT_HUGEPAGE——THP 不可用时不会创建 large folios。注意在 grub 里关 THP 不等于关 large folios。
自查清单
- 内核/文件系统:XFS 5.18+,或 ext4 6.16+;
- readahead:
cat /sys/block//queue/{read_ahead_kb,optimal_io_size}; - 大映射:
/proc/meminfo里的FilePmdMapped,或用 smaps 看 gcache 映射;/proc/vmstat里的thp_file_mapped是否在涨; - 周期性 burst:
iostat里大约每 30–35 秒一次; - 按线程计量:
pidstat -d看各线程的写入量是否为 2MB 的整数倍。
链接
- PXC bug:PXC-5340
- systemd issue:systemd/systemd#40262
- 深入分析:folios-mmap
- 原文:When a Small Change Writes Too Much