案例 PXC 升到 8.0 后每 30 秒一阵巨量磁盘写:Linux 大 folio 遇上 mmap 的 Galera cache

2026-10-26 21:05:55

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 GB61%
galera_recv-05.2 GB24%
ib_pg_flush*2.0 GB9%
ib_log_writer0.76 GB4%
Galera_srvc0.29 GB1%

第一个怀疑是 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 的整数倍。

链接

复制全文 生成海报 MySQL 运维 Linux 性能排查

推荐文章

程序员茄子在线接单