PXC 数据目录堆满 gcache.page 文件:原因、排查与安全恢复

2026-09-17 26 预计阅读时间: 1 分钟
来源: percona.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:12 分钟

在 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_downtowsrep_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';

这不是应该盲目执行的“清理命令”。在修改前,应先确认:

  1. 没有 joiner 正在从该节点接收 IST;
  2. 其他 PXC 节点处于 Synced 状态;
  3. 已记录原始 wsrep_provider_options
  4. 当前 PXC/Galera 版本支持动态修改这一 provider 选项。

修改后继续观察文件数量和磁盘占用。如果 purge 仍然没有推进,或者磁盘已经逼近满载,更可控的方式通常是让该节点离线,清理可重建的 GCache,再从健康 donor 重新加入。

一套以 IST 为目标的离线恢复流程

下面是一套可以改造的操作流程。假设:

  • 三节点 PXC 中至少有两个健康节点;
  • pxc-2 是已确认的健康 donor;
  • 异常节点使用 systemd 服务名 mysql
  • 数据目录可通过 SELECT @@datadir 获取。

1. 记录状态并确认 donor

在健康节点 pxc-2 上执行前面的 wsrep 状态查询,确认它是 PrimarySyncedReady。同时保存异常节点的状态文件信息:

sudo cat /var/lib/mysql/grastate.dat

不要手工修改 seqnosafe_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_downtowsrep_last_committed 的差距;
  • wsrep_local_state_commentwsrep_ready 和集群大小;
  • IST/SST 的开始、完成与失败日志;
  • gcache.freeze_purge_at_seqno 是否长时间停留在异常值。

处理这类故障的核心不是“删掉几千个文件”,而是确认为什么 purge 被冻结、保护一个可用 donor,并让异常节点通过 IST 或必要时的 SST 安全回到集群。只要保留至少一个状态完整、缓存范围足够的健康节点,恢复过程就能从紧急磁盘清理变成可控的集群维护。


相关推荐