GaussDB集中式xlog堆积致磁盘满的排障实战
前两周半夜两点多监控把微信告警刷成了全红一套 GaussDB 数据库节点数据盘使用率从 85% 一路拉满登录主机以后顺着告警往下找发现pg_xlog目录下面堆了两万多个 WAL 文件单文件 16M加起来快 200G。这台机器的实例跑的是 GaussDB 505.2.1 集中式架构版本不算新但生产环境里用得非常稳平时也没人专门去盯 xlog 目录。可一旦堆积起来轻则磁盘满、实例 hang 住重则主需要紧急切换整个业务链路都会受影响。这个标题里的“xlog”对很多刚接触 GaussDB 的人可能有点陌生其实就是 PostgreSQL 体系里的 WALWrite-Ahead Logging日志文件。在 GaussDB 集中式环境里它保留在数据目录下的pg_xlog子目录中作用只有一个保证崩溃恢复不丢数据。听起来很简单但真出问题的时候你不会第一眼觉得是它——因为数据库进程还在跑业务查询也看不出异常只有磁盘空间在一点点往下掉。这篇文章就把我这次排障的过程、遇到的坑、以及最后怎么从根上把问题压住的方法完整梳理一遍给同样被 xlog 堆满磁盘的朋友一个可以直接照抄的排障路径。1. 先搞清楚 xlog 堆积到底堆在哪里1.1 xlog 是怎么被回收的xlog 的生成和回收逻辑简单说就是一环扣一环的流水线。数据库每做一次修改先把变更记录写入 WAL 缓冲再由 WAL writer 进程刷到磁盘的 WAL 文件里。当文件写满一个 segment通常是 16M就切换出新的 WAL 文件旧的 WAL 文件并不会立刻被删掉而是一直保留到“所有人都认为它不再需要了”为止。判断“不再需要”有四个条件缺一不可该文件已经被归档如果开启了归档模式所有复制槽replication slot的restart_lsn都已经越过这个文件所有备机都已经消费到这个位置之后已经完成一次涵盖该文件内容的 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 文件就很难被删除。activefalse的复制槽是 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。恢复归档通道的常规操作清理归档目录的空间或者调整归档命令指向新的可写目录手动在 shell 里执行一次cp命令确认同一条命令可以执行成功等 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之前设得偏小比如说只有 1Gcheckpoint 几乎每分钟都在触发每次都要刷大量脏页系统 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为 streamingreplay_lag只有几十 Mpg_replication_slots有一个物理复制槽active为 falserestart_lsn落后当前 LSN 大概 80G这就是最直接的证据pg_stat_archiverlast_archived_time时间正常failed_count没有增长pg_stat_bgwritercheckpoint 耗时正常没有明显 IO 异常。看到这个组合基本判定问题是复制槽死掉导致的死堆积。这个槽位对应的是很早之前做过一套逻辑复制测试残留的槽位业务已经完全不使用它了。4.2 检查结果和结论当时在pg_replication_slots中看到的这条槽slot_type为 physicalactivefalse没有任何备机的application_name和它对应。我又和负责这套环境的同事确认该测试节点已经下线超过两个月不需要再保留复制关系。结论明确以后并没有马上 DROP。我先把当前 WAL 位置和restart_lsn之间的大小差记录到运维备注里又做了一次主备全量状态快照然后执行删除SELECT pg_drop_replication_slot(slot_test1);执行成功以后我立刻检查了pg_xlog目录的大小。因为归档和备机都正常CHECKPOINT 本身也在正常跑所以删除槽位后WAL 清理条件全部满足目录体积马上开始下降。整个过程大概过了一个多小时pg_xlog从 190G 降到了 30G 左右磁盘告警也自动恢复了。4.3 执行处置和验证为了让清理过程不因为某个 checkpoint 的周期性延迟而拖很久我还在维护窗口内执行了一次手动 checkpointCHECKPOINT;这条命令会调度一次真正的 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 之前要确认哪些事删除复制槽是一次性操作执行之前一定要冷静确认。我个人的检查顺序是先看pg_replication_slots里的slot_name是否能在当前主备配置里找到对应关系看active是否为 false如果为 true先排查对应备机是不是正在使用去主机的pg_stat_replication找有没有 matching 的application_name和运维同事确认该节点是否已经下线或还没有上线将restart_lsn和当前 LSN 记录下来作为操作前后的对账依据。只要这些确认都通过删除槽位的风险就已经被压到很低。5.3 关于参数调整的时机和副作用在彻底定位根因之前不要先动参数。我见过很多人一看到磁盘告警就先把wal_keep_segments调大这会让堆积问题暂时被掩盖同时磁盘风险进一步放大。参数调整必须是问题定位后的选择而不是排查前的试探。如果确实需要调整参数尽可能在维护窗口或者业务低峰期操作并且每个参数单独调整避免多个变量混在一起。修改后要关注主备两端的进程日志确认没有报错。5.4 考虑是否要重启或切换节点如果磁盘已经满到数据库无法写入那第一步应该是防止实例挂掉而不是继续排查。这时候优先看备机是否健康如果备机正常可以考虑直接切换主备让原主库在离线状态下慢慢处理堆积文件。如果备机也不健康只能在主库上做最小化止损比如清理无效档案、释放归档目录空间但不要触碰pg_xlog。切换主备不是小事要综合业务容忍度、切换脚本的可靠性和数据同步延迟来决策。如果没有把握宁可先限流一部分业务也别贸然切换后造成更大的失控。6. 长期监控与预防建议6.1 监控项一场排障下来最能防止下次再犯的就是把监控做在前面。除了常规的实例状态和磁盘空间监控外我建议至少在数据库层面额外监控以下几项pg_xlog目录下文件数和总大小按小时采集pg_stat_replication中每个节点的replay_lagpg_replication_slots中activefalse的槽位数量和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 这条链路一步步查大部分堆积都能在十分钟内锁定方向。数据库这套系统说到底没那么玄每个文件都有它存在的理由及时清理的机制也都写在了源代码里你要做的就是找到那个把它堵住的环节。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →