☰
GaussDB集中式xlog堆积致磁盘满的排障实战
2026/10/1 3:47:50 网站建设 项目流程

前两周半夜两点多,监控把微信告警刷成了全红:一套 GaussDB 数据库节点数据盘使用率从 85% 一路拉满,登录主机以后顺着告警往下找,发现pg_xlog目录下面堆了两万多个 WAL 文件,单文件 16M,加起来快 200G。这台机器的实例跑的是 GaussDB 505.2.1 集中式架构,版本不算新,但生产环境里用得非常稳,平时也没人专门去盯 xlog 目录。可一旦堆积起来,轻则磁盘满、实例 hang 住,重则主需要紧急切换,整个业务链路都会受影响。

这个标题里的“xlog”对很多刚接触 GaussDB 的人可能有点陌生,其实就是 PostgreSQL 体系里的 WAL(Write-Ahead Logging)日志文件。在 GaussDB 集中式环境里,它保留在数据目录下的pg_xlog子目录中,作用只有一个:保证崩溃恢复不丢数据。听起来很简单,但真出问题的时候,你不会第一眼觉得是它——因为数据库进程还在跑,业务查询也看不出异常,只有磁盘空间在一点点往下掉。这篇文章就把我这次排障的过程、遇到的坑、以及最后怎么从根上把问题压住的方法完整梳理一遍,给同样被 xlog 堆满磁盘的朋友一个可以直接照抄的排障路径。

1. 先搞清楚 xlog 堆积到底堆在哪里

1.1 xlog 是怎么被回收的

xlog 的生成和回收逻辑,简单说就是一环扣一环的流水线。数据库每做一次修改,先把变更记录写入 WAL 缓冲,再由 WAL writer 进程刷到磁盘的 WAL 文件里。当文件写满一个 segment(通常是 16M)就切换出新的 WAL 文件,旧的 WAL 文件并不会立刻被删掉,而是一直保留到“所有人都认为它不再需要了”为止。

判断“不再需要”有四个条件,缺一不可:

  1. 该文件已经被归档(如果开启了归档模式);
  2. 所有复制槽(replication slot)的restart_lsn都已经越过这个文件;
  3. 所有备机都已经消费到这个位置之后;
  4. 已经完成一次涵盖该文件内容的 checkpoint,确认崩溃恢复时用不到它了。

所以正常情况下,pg_xlog目录的体积是一个动态平衡值,大概在几个max_wal_size的范围内。如果目录体积持续涨、涨到几十甚至上百 G,说明某一条回收链路卡住了。最常见的卡点就是归档失败、复制槽不推进、备机追不回、或者 checkpoint 跟不上写入速度。一句话总结就是:WAL 写不进去了才会出问题,但 WAL 删不掉才是堆积问题的本质。

1.2 堆积的判断方法:先别看参数,先看数据和位置

很多人的第一反应是去看max_wal_size,觉得是不是参数设得太大。这个方向不算完全错,但确实把主次搞反了。max_wal_size只是一个软触发上限,不是“xlog 只能积累到这么大”的硬限制。它会影响 checkpoint 的触发频率,但不会限制最终文件数量。真正要看的是“当前的 WAL 位置”和“回收点”的距离。

用一条命令就能看到大概距离:

SELECT pg_size_pretty( pg_xlog_location_diff(pg_current_xlog_location(), '0/0') ) AS current_wal_offset;

这能看到当前 WAL 已经写到了什么位置。如果这个值并不大,但pg_xlog目录里塞了几百个文件,那说明是在“死堆积”:WAL 根本没有快速增长,只是旧的清不掉。反过来,如果这个值一直在涨,而且增长速度和业务写入量成正比,那是“活堆积”,需要从 checkpoint 和归档链路往下查。

然后直接到文件系统层面看段文件:

ls -lh $PGDATA/pg_xlog | tail -20

注意看一下切到新文件的频率和目录里的文件数。如果一个 16M 的文件可以切出一堆时间戳非常相近的文件,说明数据库在短时间内产生了大量事务日志,这时候要配合业务侧看是否有大批量更新、全表删除或者索引重建等操作。

1.3 集中式 GaussDB 的特殊性

GaussDB 集中式形态虽然是单主,但生产环境经常还会配一主一备。和分布式形态不同,集中式没有多个 DN 分担 WAL 压力,主库上所有业务变更都集中在同一个 WAL 序列里。一旦备机断开,或者复制槽处于 dead 状态,主库的 WAL 回收就会直接被拖住。

这次遇到的版本是 GaussDB 505.2.1,它的数据目录下边还叫pg_xlog,而不是 PG 14 之后的pg_wal。如果你是在新版本上排查,指令名称会有些差异,但底层逻辑是通用的。后面写到的所有命令,都是以这个 505.2.1 版本为背景;如果你的环境是更新的版本,注意把pg_xlog对应替换成pg_wal,把pg_switch_xlog对应替换成pg_switch_wal即可。

2. 问题排查:从磁盘到进程,一层层定位

2.1 看目录、看文件、看空间

排查的第一步肯定不是连数据库,而是先到操作系统层面确认现状:

df -h du -sh $PGDATA/pg_xlog ls $PGDATA/pg_xlog | wc -l

这一步主要确认三件事:磁盘是不是真的被 WAL 文件占满、pg_xlog目录里到底有多少文件、以及这些文件的大小和修改时间是否异常。如果修改时间全都是最近几小时,说明数据库仍然活跃;如果大量文件停留在几天前,说明清理已经停了很久,磁盘问题只是在某一次备份或 IO 抖动后被点爆。

在集中式环境里,实例目录下可能有多个子目录,注意别把备机的数据目录当成主库的目录。用gs_ctl query -D data_dir或查看监听端口对应的数据目录,确保你在排查的是同一套主备关系里的主节点。

2.2 看主备复制状态:slot 和 lag

连到主库执行:

SELECT pid, application_name, state, sync_state, sent_lsn, write_lsn, flush_lsn, replay_lsn, pg_size_pretty(pg_xlog_location_diff(pg_current_xlog_location(), replay_lsn)) AS replay_lag FROM pg_stat_replication;

重点关注state、sent_lsn和replay_lsn。正常情况下replay_lsn应该跟在sent_lsn后面不远,双方差值只在小数级别。如果备机的replay_lsn落后主库几个 G,说明备机长时间没有回放 WAL,这不仅仅会拖慢主库的清理,还会让备机上的业务只读查询变得非常慢。

然后看复制槽:

SELECT slot_name, slot_type, active, restart_lsn, pg_size_pretty(pg_xlog_location_diff(pg_current_xlog_location(), restart_lsn)) AS lag_size FROM pg_replication_slots;

这里的restart_lsn是主库为了这个栅格保留 WAL 的起点。如果restart_lsn远远落后于当前 WAL,并且active为 false,那么 WAL 文件就很难被删除。active=false的复制槽是 xlog 堆积的头号嫌疑对象,常见于备机做过重建、替换,或者旧槽位没有及时清理。

2.3 看归档:最常见也最容易被忽视

在上一节确认复制槽正常以后,再去看归档状态:

SELECT * FROM pg_stat_archiver;

这个视图会直接告诉你归档进程最近一次成功归档的时间、失败次数、最后一次失败的时间和失败原因。如果failed_count一直在增长,或者last_failed_time离当前时间很近,那基本可以断定归档链路断了。

再配合看参数:

SHOW archive_mode; SHOW archive_command;

GaussDB 默认不会把archive_command打印太详细,但你可以手动在 shell 里执行归档命令对应的 cp 或 scp 语句,看看是权限问题、目录不可写、网络不通,还是存储满导致的。

2.4 看长事务和 2PC

长事务虽然不会直接阻止 WAL 文件被删除,但它会让数据库的清理工作一直卡住,导致 WAL 生成速度降不下来,也容易在故障排查时带偏方向。

查询活跃事务:

SELECT pid, state, xact_start, now() - xact_start AS duration, wait_event, query FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_start;

如果有事务已经跑了几小时甚至几天,建议先和业务确认是否可以终止。长事务拖着不提交,会导致 VACUUM 无法及时清理死元组,进一步导致 WAL 写入持续增长,看起来就像 xlog 堆积。

同样的,两阶段事务也要检查:

SELECT gid, prepared, owner, database, transaction FROM pg_prepared_xacts;

两阶段事务在 GaussDB 集中式里不常见,但一旦出现因为应用故障未提交的 prepare 事务,对 WAL 回收的影响是明摆着的。它会让事务 XID 一直不能回收,触发保守的清理策略,增加 WAL 文件保留的惯性。

2.5 看 checkpoint 与 IO 状态

最后回到数据库自身的检查点进程:

SELECT checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time, buffers_checkpoint FROM pg_stat_bgwriter;

如果checkpoint_write_time的数值特别夸张,说明每次 checkpoint 刷脏页耗时很长,或者底层存储 IO 出现了严重瓶颈。checkpoint 越慢,能安全废弃的 WAL 文件就越少,而新事务又在不停地写,于是两个因素叠加起来,xlog 目录就会像只吃不拉一样持续膨胀。

这里再强调一次:max_wal_size不能直接作为判断堆积的指标。它太小会导致 checkpoint 频繁触发,增大 IO 压力;它太大会让 WAL 文件在正常波动时也保留很多。合理的做法是先看pg_stat_bgwriter里checkpoints_req和checkpoints_timed的比值,再看单个 checkpoint 的耗时,然后才去调整参数。

3. 根因拆解与处置方案

3.1 归档失败引起的堆积:先保归档,再让 checkpoint 消化

我这次遇到的情况里,归档因素最典型。当时pg_stat_archiver里failed_count一直在涨,但last_failed_wal全是同一个文件,说明归档命令执行失败后,进程反复重试但都卡在同一处。去检查备份目录,果然是备份盘满了,归档文件拷不进去。

归档失败为什么会导致 xlog 无法清理?因为数据库在做 checkpoint 的 WAL 清理时会判断:如果开了归档,必须确认 WAL 文件已经被归档成功,否则不能废弃。这是为了 PITR 恢复的完整性。所以只要归档命令一直失败,xlog 目录就会一直保留旧日志,哪怕业务已经不太活跃。

处置顺序很重要:先恢复归档通道,再让数据库自己清理,不要手动去删 xlog。

恢复归档通道的常规操作:

  1. 清理归档目录的空间,或者调整归档命令指向新的可写目录;
  2. 手动在 shell 里执行一次cp命令,确认同一条命令可以执行成功;
  3. 等 archiver 进程自动重试,或者执行一次 WAL 切换:
SELECT pg_switch_xlog();

新的 WAL 文件会触发归档调度,归档成功之后,后面的 checkpoint 就能把大量旧文件清掉。注意,pg_switch_xlog()不保证一次切换就能处理掉几百个堆积文件,archiver 会按顺序逐个归档,整个过程可能需要一段时间。如果堆积非常严重,可以先只处理归档链路,然后设置窗口让它在后台慢慢消化,不要频繁手动切换,把 archiver 进程搞得更忙。

3.2 复制槽不消费导致的堆积:确认槽位归属后 DROP

复制槽造成的堆积比归档失败更容易判断,因为pg_replication_slots直接暴露了问题。有一种常见场景:某个复制槽是备机建复制时创建的,但后来备机重建过,或者主备切换后旧槽位没有同步清理,结果这个槽在主机上的restart_lsn永远停在很久以前。

如果确认这个槽对应的节点已经不存在,或者已经不再需要有 WAL 保留的需求,可以删除它:

SELECT pg_drop_replication_slot('slot_name');

这里必须谨慎。删除复制槽是一个不可逆操作,如果槽位后面还有备机在依赖它,备机会立刻追不上主库,甚至直接断开复制关系。所以在执行之前,至少确认两件事:

  • 从pg_replication_slots看active是否为 false,或者虽然为 true 但对应的application_name已经不在主机的pg_stat_replication中;
  • 从全局拓扑确认该槽位对应的节点已经删除、停产或不再承载复制任务。

如果只是备机暂时离线,但之后还要继续使用,不要 drop slot,应该先恢复备机,让备机重新连接消费 WAL,等restart_lsn追上以后,堆积自然解除。生产环境里很多人安全意识很高,宁可留着槽位也不愿贸然删除,这点我是认可的。但反过来,如果你确认槽位已经变成死数据,那该删就删,否则以后每次磁盘告警都要重新经历一遍这次排障。

3.3 备机追不上导致的堆积:重建备机 vs 扩大保留

备机长时间追不上 WAL,也会导致主库保留大量 xlog。原因很简单:主库担心备机还要请求更早的 WAL,所以不敢回收。这时候需要判断备机到底能不能追回来。

如果网络抖动恢复、备机短暂的延迟很快,那就等它自己回放完成。如果备机的replay_lsn落后了几个 G,而且每天都在原地踏步,最稳的策略是重建备机,而不是无脑调整wal_keep_segments或max_wal_size去迁就它。

重建备机时要先和备机侧确认,已经落后到一定程度后,从主库拉全量备份再重新 build 比让它继续追成本更低。这个判断阈值没有绝对标准,一般落后超过一天的 WAL 量,同时主库已经堆积超过几十个 G,重建通常比继续追要划算。

重建备机的核心动作是用gs_ctl build命令重新建立备机数据目录。这个过程会在覆盖备机现有数据,所以操作前必须做好独苗和备份,并且确认主备关系切换脚本不会在这期间自动执行。重建完成后,旧的复制槽如果没有被新 build 过程复用,就需要回到主库把它清理掉,避免变成死槽。

3.4 checkpoint 跟不上、参数配置不当:调姿势,不当背锅侠

如果归档正常、复制槽也没问题,但 xlog 依然在涨,那多半是 checkpoint 跟不上 WAL 生成速度。常见的情况是业务做了一次超大事务批量更新,生成了几百 G 的 WAL,而单个 checkpoint 要刷的脏页太多,完成耗时过长,导致 WAL 清理滞后。

这时可以考虑适当调大max_wal_size,让 checkpoint 不要被触发的那么频繁,给后端一个大缓冲空间。听起来这个动作是在增加 xlog 保留,但如果max_wal_size之前设得偏小,比如说只有 1G,checkpoint 几乎每分钟都在触发,每次都要刷大量脏页,系统 IO 被打满,反而让 WAL 堆积更严重。调大到 8G 或 16G 以后,checkpoint 的触发变得平缓,整体吞吐反而更好。

wal_keep_segments这个参数也要重点看。如果它设得过大,比如 1024,那么哪怕复制槽、归档、备机全部正常,主库也一定会保留最近 1024 个 WAL 文件。这个值是很多 DBA 为了备机追日志而设置的,但一旦业务结构变化,它就变成长期堆积的来源之一。

参数调整之后,要观察至少一个完整 checkpoint 周期,不要立刻判断有效。同时注意,这种参数是全局性的,修改前咨询业务、观察峰值,最好在维护窗口操作。

3.5 备份残留导致的堆积

还有一种不太常见但很坑的情况:备份会话没有正常结束。比如通过pg_start_backup()做外部备份时,如果执行到一半进程被杀掉,或者备份脚本异常退出,主库会一直认为备份还在进行中,从而保留备份开始点之后的全部 WAL。

排查方式主要是看pg_backup_label文件是否存在。正常备份结束后该文件会被自动清理,如果还在,大概率是 backup 会话没有收尾。如果经过确认没有正在进行的备份,需要根据备份状态判断是否能清理,而不是简单删除文件。最安全的办法是使用数据库本身的备份接口重新执行一次完整的 start/stop 流程。

4. 一次实际处理的完整动作记录

4.1 现场信息和告警

回到这次实战里。接到告警时,磁盘使用率已经从 85% 涨到了 92%,架构是 GaussDB 505.2.1 集中式一主一备。业务侧反馈应用没有明显报错,但查询延迟变大,CPU 使用率也开始上升。连上主库后,我先把所有核心视图检查了一遍,结果如下:

  • pg_stat_replication:备机连接正常,state为 streaming,replay_lag只有几十 M;
  • pg_replication_slots:有一个物理复制槽,active为 false,restart_lsn落后当前 LSN 大概 80G,这就是最直接的证据;
  • pg_stat_archiver:last_archived_time时间正常,failed_count没有增长;
  • pg_stat_bgwriter:checkpoint 耗时正常,没有明显 IO 异常。

看到这个组合,基本判定问题是复制槽死掉导致的死堆积。这个槽位对应的是很早之前做过一套逻辑复制测试残留的槽位,业务已经完全不使用它了。

4.2 检查结果和结论

当时在pg_replication_slots中看到的这条槽,slot_type为 physical,active=false,没有任何备机的application_name和它对应。我又和负责这套环境的同事确认,该测试节点已经下线超过两个月,不需要再保留复制关系。

结论明确以后,并没有马上 DROP。我先把当前 WAL 位置和restart_lsn之间的大小差记录到运维备注里,又做了一次主备全量状态快照,然后执行删除:

SELECT pg_drop_replication_slot('slot_test1');

执行成功以后,我立刻检查了pg_xlog目录的大小。因为归档和备机都正常,CHECKPOINT 本身也在正常跑,所以删除槽位后,WAL 清理条件全部满足,目录体积马上开始下降。整个过程大概过了一个多小时,pg_xlog从 190G 降到了 30G 左右,磁盘告警也自动恢复了。

4.3 执行处置和验证

为了让清理过程不因为某个 checkpoint 的周期性延迟而拖很久,我还在维护窗口内执行了一次手动 checkpoint:

CHECKPOINT;

这条命令会调度一次真正的 checkpoint,让符合条件的 WAL 文件可以立即被回收。但不要过度依赖它,频繁手动CHECKPOINT会增加 IO 压力,正常情况下等着自动 checkpoint 即可。

验证环节里我用了三条命令:

SELECT slot_name, active, restart_lsn FROM pg_replication_slots; SELECT * FROM pg_stat_archiver; SELECT pg_size_pretty(pg_xlog_location_diff(pg_current_xlog_location(), '0/0'));

第一条确保坏槽位已经不存在;第二条确保归档进程没有受影响;第三条确认 WAL 位置没有因为删除槽位而产生异常跳变。全部正常后,这起堆积事故才算真正闭环。

5. 这些坑我提前帮你踩了(注意事项)

5.1 不要删文件,不要靠磁盘管理软件直接清理

这是 xlog 堆积事件里最容易踩的坑。磁盘快满的时候,很多人第一反应是往pg_xlog目录里看一眼,然后手动rm掉一堆看起来没用的文件。这种做法极其危险,因为主库上的 WAL 不仅仅代表“日志”,还代表崩溃恢复和数据同步的基准点。删掉一个还没有被归档、或者备机还没消费的文件,轻则备机卡住,重则主库无法恢复,数据风险完全不可控。

如果你实在没有其他办法,必须做手工清理,也要先把文件备份到其他存储,再通过数据库侧的pg_archivecleanup工具去清理已归档的 WAL,而不是直接rm。更重要的是,一定要先判断“为什么不能自动清理”,因为治标不治本的话,就算这次腾出空间,过两天又会被同一个原因重新填满。

5.2 DROP SLOT 之前要确认哪些事

删除复制槽是一次性操作,执行之前一定要冷静确认。我个人的检查顺序是:

  1. 先看pg_replication_slots里的slot_name是否能在当前主备配置里找到对应关系;
  2. 看active是否为 false,如果为 true,先排查对应备机是不是正在使用;
  3. 去主机的pg_stat_replication找有没有 matching 的application_name;
  4. 和运维同事确认该节点是否已经下线或还没有上线;
  5. 将restart_lsn和当前 LSN 记录下来,作为操作前后的对账依据。

只要这些确认都通过,删除槽位的风险就已经被压到很低。

5.3 关于参数调整的时机和副作用

在彻底定位根因之前,不要先动参数。我见过很多人一看到磁盘告警就先把wal_keep_segments调大,这会让堆积问题暂时被掩盖,同时磁盘风险进一步放大。参数调整必须是问题定位后的选择,而不是排查前的试探。

如果确实需要调整参数,尽可能在维护窗口或者业务低峰期操作,并且每个参数单独调整,避免多个变量混在一起。修改后要关注主备两端的进程日志,确认没有报错。

5.4 考虑是否要重启或切换节点

如果磁盘已经满到数据库无法写入,那第一步应该是防止实例挂掉,而不是继续排查。这时候优先看备机是否健康,如果备机正常,可以考虑直接切换主备,让原主库在离线状态下慢慢处理堆积文件。如果备机也不健康,只能在主库上做最小化止损,比如清理无效档案、释放归档目录空间,但不要触碰pg_xlog。

切换主备不是小事,要综合业务容忍度、切换脚本的可靠性和数据同步延迟来决策。如果没有把握,宁可先限流一部分业务,也别贸然切换后造成更大的失控。

6. 长期监控与预防建议

6.1 监控项

一场排障下来,最能防止下次再犯的就是把监控做在前面。除了常规的实例状态和磁盘空间监控外,我建议至少在数据库层面额外监控以下几项:

  • pg_xlog目录下文件数和总大小,按小时采集;
  • pg_stat_replication中每个节点的replay_lag;
  • pg_replication_slots中active=false的槽位数量和restart_lsn滞后量;
  • pg_stat_archiver的failed_count增长量和last_archived_time是否持续更新;
  • checkpoint 的耗时和触发频率。

其中任何一项出现异常,都不一定立刻引发磁盘满,但它就是 xlog 堆积的前置信号。提前感知这些信号,就不用在灾害发生之后抢救。

6.2 建议参数基线

针对 GaussDB 505.2.1 集中式一主一备的常见场景,我通常会让配置保持在一个相对保守但安全的范围:

参数建议值说明
archive_modeon长期开启,保证 PITR 能力
archive_timeout300高业务量下可设为 60,避免归档延迟
wal_keep_segments16~64视备机回放速度而定,不要设置成几百
max_wal_size8G~16G降低高频 checkpoint 的 IO 压力
checkpoint_timeout900与 max_wal_size 配合调整

这里给的是基线参考值,不是放之四海而皆准的标准。业务写入模型差别很大,调整后需要观察一个完整周期,看检查点频率是否合理、备机是否稳定。

6.3 配合备份和高可用方案

xlog 堆积问题的最终预防,还是要在高可用和备份机制上做整体设计。备份链路要单独设监控,特别是归档目录的空间,不能被其他数据占满;备份脚本要有异常退出后的清理逻辑,避免因为备份残留导致 WAL 无法回收。主备关系变更后,要及时清理无用的复制槽,定期做一次拓扑梳理。

也可以写一个简单的巡检脚本,每天凌晨查一次pg_replication_slots和pg_stat_archiver,输出异常状态。脚本本身不用很复杂,关键是养成习惯:对数据库而言,xlog 的“健康”不是靠出事后再抢救,而是靠每天的巡检兜底。


最后再分享一个我对 xlog 堆积问题的心得。处理过好几起同类问题后,我发现它最大的难处不是技术,而是现场心态。磁盘告警一旦触发,业务和运维都在催,而 xlog 又不像锁等待、慢查询那样能直观定位根因,很容易让人病急乱投医,手动删文件或者无脑切换节点。实际上,只要稳住节奏,按归档、复制、checkpoint 这条链路一步步查,大部分堆积都能在十分钟内锁定方向。数据库这套系统说到底没那么玄,每个文件都有它存在的理由,及时清理的机制也都写在了源代码里;你要做的,就是找到那个把它堵住的环节。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询