在 Percona XtraDB Cluster(PXC)节点上看到成千上万个 gcache.page.* 文件时,问题通常不只是“缓存配置得太大”。更值得警惕的是:GCache 清理可能被冻结,旧页面无法回收,最终持续吞噬 MySQL 数据盘。
这些文件不能在运行中的节点上直接批量删除。更稳妥的处理方式是先确认集群和 IST 条件,再让异常节点离开集群、离线清理 GCache,并从健康节点重新加入。
gcache.page.* 为什么会不断增加
Galera GCache 用于保存近期复制写集。当某个节点短暂离线后重新加入,只要 donor 的 GCache 仍覆盖缺失的序列号范围,就可以使用 IST(Incremental State Transfer,增量状态传输),避免成本更高的 SST。
除了主要的 GCache 文件,Galera 还可能创建 gcache.page.* 页面文件。少量页面文件并不一定异常,但下面几个现象同时出现时,通常意味着清理没有正常推进:
- 文件数量持续增加,而不是短暂波动;
- 数据目录占用不断上升;
gcache.page.*长时间不被删除;wsrep_provider_options中的gcache.freeze_purge_at_seqno不是正常的未冻结状态;- 节点曾作为 IST donor,或者 IST 流程异常中断。
GCache purge 被冻结后,provider 会保留特定序列号之后所需的写集页面,以免正在进行的 IST 丢失数据来源。如果冻结状态没有解除,旧页面便无法被回收。此时,即使配置了 gcache.size,磁盘实际占用也可能继续增长,因为页面存储和被冻结的写集不能简单地按环形缓存上限立即淘汰。
大事务也可能推动页面文件增长,但“长期保留数千个页面文件”与“偶尔出现几个页面文件”是两个不同的问题。判断重点应放在数量趋势、purge 状态和 IST 历史上。
先确认集群状态,不要急着删除文件
先在异常节点和至少一个健康节点上检查 wsrep 状态:
SHOW GLOBAL STATUS WHERE Variable_name IN (
'wsrep_cluster_status',
'wsrep_connected',
'wsrep_ready',
'wsrep_local_state_comment',
'wsrep_local_state_uuid',
'wsrep_last_committed',
'wsrep_local_cached_downto'
);
SHOW GLOBAL VARIABLES LIKE 'wsrep_provider_options';
SHOW GLOBAL VARIABLES LIKE 'wsrep_sst_donor';
健康 donor 通常应满足:
wsrep_cluster_status = Primary;wsrep_connected = ON;wsrep_ready = ON;wsrep_local_state_comment = Synced;- donor 与待恢复节点属于相同的集群状态 UUID;
- donor 的 GCache 覆盖待恢复节点缺失的序列号范围。
粗略判断 IST 范围时,可以对比 donor 的 wsrep_local_cached_downto 和 wsrep_last_committed。如果 joiner 所需的下一个序列号落在这个区间内,才具备 IST 的基础条件。最终是否执行 IST 仍由 Galera 根据实际状态判断,不能只凭一个状态变量保证。
下面的脚本可以统计页面文件数量、总空间和最老文件。运行前请确认当前账号能够读取 MySQL 数据目录:
#!/usr/bin/env bash
set -euo pipefail
MYSQL=(mysql --batch --skip-column-names)
DATADIR="$(${MYSQL[@]} -e 'SELECT @@datadir')"
echo "MySQL datadir: ${DATADIR}"
echo -n 'Page file count: '
find "$DATADIR" -maxdepth 1 -type f -name 'gcache.page.*' -printf '.' | wc -c
echo 'Disk usage:'
du -ch "$DATADIR"/gcache.page.* 2>/dev/null | tail -n 1 || true
echo 'Oldest page files:'
find "$DATADIR" -maxdepth 1 -type f -name 'gcache.page.*' \
-printf '%T@ %TY-%Tm-%Td %TH:%TM:%TS %s %p\n' \
| sort -n | head -n 10
建议每隔几分钟重复执行一次。如果文件数和空间稳定下降,purge 可能已经恢复;如果持续单向增长,就需要进一步处理。
解除冻结,还是重启并重新加入
如果确认当前没有正在执行的 IST,并且 provider 的 purge 冻结状态明显残留,可以评估将其恢复为未冻结值。常见操作形式如下:
SET GLOBAL wsrep_provider_options = 'gcache.freeze_purge_at_seqno=-1';
这不是应该盲目执行的“清理命令”。在修改前,应先确认:
- 没有 joiner 正在从该节点接收 IST;
- 其他 PXC 节点处于
Synced状态; - 已记录原始
wsrep_provider_options; - 当前 PXC/Galera 版本支持动态修改这一 provider 选项。
修改后继续观察文件数量和磁盘占用。如果 purge 仍然没有推进,或者磁盘已经逼近满载,更可控的方式通常是让该节点离线,清理可重建的 GCache,再从健康 donor 重新加入。
一套以 IST 为目标的离线恢复流程
下面是一套可以改造的操作流程。假设:
- 三节点 PXC 中至少有两个健康节点;
pxc-2是已确认的健康 donor;- 异常节点使用 systemd 服务名
mysql; - 数据目录可通过
SELECT @@datadir获取。
1. 记录状态并确认 donor
在健康节点 pxc-2 上执行前面的 wsrep 状态查询,确认它是 Primary、Synced 和 Ready。同时保存异常节点的状态文件信息:
sudo cat /var/lib/mysql/grastate.dat
不要手工修改 seqno 或 safe_to_bootstrap。如果节点是异常退出,seqno 可能为 -1,此时可能需要让 PXC 启动流程执行 wsrep recovery 来恢复真实位置,而不是自行猜测序列号。
2. 停止异常节点
sudo systemctl stop mysql
sudo systemctl is-active mysql || true
确认 mysqld 已完全退出:
pgrep -a mysqld || true
不要在 mysqld 仍运行时删除 gcache.page.*。provider 可能仍持有这些文件,在线删除既可能干扰 IST,也可能让磁盘和进程看到不一致的状态。
3. 将 GCache 文件移到隔离目录
与直接 rm 相比,先移动文件更容易回滚。下面的命令会保留业务数据、grastate.dat 和 redo/undo 文件,只隔离可重建的 GCache 文件:
set -euo pipefail
DATADIR="/var/lib/mysql"
STAMP="$(date +%Y%m%d-%H%M%S)"
QUARANTINE="/var/lib/mysql-gcache-quarantine/${STAMP}"
sudo install -d -m 700 "$QUARANTINE"
if [ -f "$DATADIR/galera.cache" ]; then
sudo mv "$DATADIR/galera.cache" "$QUARANTINE/"
fi
sudo find "$DATADIR" -maxdepth 1 -type f -name 'gcache.page.*' \
-exec mv -t "$QUARANTINE" -- {} +
sudo du -sh "$QUARANTINE"
sudo ls -lah "$QUARANTINE" | head
将 galera.cache 与页面文件一起隔离,可以避免新启动的 provider 继续使用与已删除页面不匹配的缓存元数据。GCache 是可重建的,但这会让该节点暂时失去作为 donor 提供旧写集的能力。
4. 指定优选 donor 并启动
配置文件路径需要按发行版调整,例如:
[mysqld]
wsrep_sst_donor=pxc-2
可以将其临时写入 /etc/mysql/mysql.conf.d/99-pxc-rejoin.cnf,然后启动节点:
sudo systemctl start mysql
sudo journalctl -u mysql -f
Galera 会根据 joiner 的持久化状态和 donor 的缓存范围自动选择 IST 或 SST。wsrep_sst_donor 只是指定优选 donor,并不能强制一个本来不满足条件的 IST。
日志中应重点查找以下信息:
sudo journalctl -u mysql --since '-15 minutes' \
| grep -Ei 'IST|SST|incremental|state transfer|gcache|wsrep'
节点加入后再次验证:
SHOW GLOBAL STATUS WHERE Variable_name IN (
'wsrep_cluster_status',
'wsrep_connected',
'wsrep_ready',
'wsrep_local_state_comment',
'wsrep_last_committed'
);
只有在节点稳定达到 Synced、业务检查正常并运行一段观察期后,才删除隔离目录:
sudo rm -rf /var/lib/mysql-gcache-quarantine/20250101-120000
请将示例时间戳替换为实际目录,并在执行 rm -rf 前再次核对路径。
IST 不一定可用,必须准备 SST 退路
清理本地 GCache 不会删除 InnoDB 业务数据,但也不会凭空创造 IST 条件。如果健康 donor 已经清除了 joiner 所需的写集,或者两者的状态 UUID 不兼容,重新加入时就只能执行 SST。
因此,操作前要同时评估:
- donor 的
wsrep_local_cached_downto是否足够早; - joiner 离线了多久、期间写入量有多大;
- SST 方法需要的磁盘、网络和凭据是否可用;
- 是否有足够空间同时容纳临时文件和状态传输;
- donor 承担状态传输时是否会影响线上延迟。
绝不要同时清理所有节点的 GCache,也不要在没有健康 Primary Component 的情况下逐台重启。推荐一次只处理一个节点,确认其回到 Synced 后再处理下一台。
收尾:把页面文件数量纳入监控
恢复完成并不代表问题彻底消失。建议增加以下监控项:
gcache.page.*文件数量和总字节数;- MySQL 数据盘剩余空间及增长速率;
wsrep_local_cached_downto与wsrep_last_committed的差距;wsrep_local_state_comment、wsrep_ready和集群大小;- IST/SST 的开始、完成与失败日志;
gcache.freeze_purge_at_seqno是否长时间停留在异常值。
处理这类故障的核心不是“删掉几千个文件”,而是确认为什么 purge 被冻结、保护一个可用 donor,并让异常节点通过 IST 或必要时的 SST 安全回到集群。只要保留至少一个状态完整、缓存范围足够的健康节点,恢复过程就能从紧急磁盘清理变成可控的集群维护。