PostgreSQL WAL 堆积与复制槽不释放排查实战

    发布时间:2026-09-26 04:32 更新时间:2026-09-26 04:32 阅读量:0

    做 PostgreSQL 主从复制或者接了逻辑订阅的库,有时会遇到一个很突然的现象:磁盘使用率一路爬升,pg_wal 目录越来越大,最后写操作开始报错,提示无法写入 WAL 或磁盘空间不足,业务直接卡住。很多人第一反应是去删 pg_wal 下的老文件,但删完没一会儿又长回来,甚至删错文件把实例搞坏。这类问题十有八九不是 WAL 生成太多,而是有个复制槽(replication slot)没有被释放,PostgreSQL 出于保护机制不能清理它还需要的那部分 WAL,只能一直留着。

    下面按“先判断是不是槽的问题,再决定怎么处理,最后补上归档与监控”的顺序讲一遍,命令都可以直接在服务器上跑。

    一、先确认 WAL 是不是被复制槽留住

    PostgreSQL 的 WAL 文件在 pg_wal(10 版本以前叫 pg_xlog)里循环使用,正常情况下旧的会被回收。但复制槽会记录一个“消费者还需要的 WAL 位置”,只要这个位置不推进,它之后的 WAL 就不能被删。所以排查第一步是看槽的状态:

    # 查看所有复制槽及其保留的 WAL 位置
    psql -U postgres -c "SELECT slot_name, slot_type, active, restart_lsn,\n  pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal\n  FROM pg_replication_slots ORDER BY restart_lsn;"
    
    

    看 pg_wal 目录实际占用

    ls -lh $PGDATA/pg_wal | tail -5 du -sh $PGDATA/pg_wal

    重点看两列:active 为 f(false)说明当前没有消费者连着,retained_wal 就是它拖住不放的 WAL 体量。如果某个槽 active=false 却保留了几个 GB,基本可以确定磁盘就是被它撑起来的。另外可以顺手确认 WAL 总量上限:

    psql -U postgres -c "SHOW max_wal_size; SHOW min_wal_size; SHOW wal_keep_size;"

    wal_keep_size(旧版本是 wal_keep_segments)是给流复制留的额外余量,它和复制槽叠加起来会放大占用。不过要提醒一句:不要为了省空间把 wal_keep_size 设成 0,那会让断线重连的从库直接追不上,需要重建。以官方文档和实际业务容忍度为准来定这个值。

    二、清理失效复制槽与补齐归档链路

    确认了是槽的问题,处理要分两种情况,别一上来就删。

    第一种,槽对应的从库或订阅端还在,只是暂时掉线。这种情况优先把消费端拉起来,让它把 WAL 追平,restart_lsn 自然推进,空间会自己释放。逻辑订阅场景可以查 pg_stat_subscription 看订阅端状态。

    第二种,槽确实已经废弃——比如那台从库早已下线、订阅被删除但槽忘了删。这时可以手动删槽,但前提是确认没有任何消费者还依赖它:

    # 删除一个确认废弃的物理复制槽
    psql -U postgres -c "SELECT pg_drop_replication_slot('slot_name');"
    
    

    逻辑复制槽(订阅已删除的情况下)

    psql -U postgres -c "SELECT pg_drop_replication_slot('sub_slot_name');"

    如果删除时报“replication slot is active”,说明还有进程占着,先查 pg_stat_replication 和 pg_stat_activity 找出是谁在用,不要强行杀掉生产连接。删完之后再复查 pg_replication_slots 和 pg_wal 占用,正常会在下一轮 checkpoint 后回落。

    还有一类更隐蔽的情况:归档(archive)没成功,WAL 也被留着。开了 archive_mode 后,如果 archive_command 一直失败,PostgreSQL 会反复重试,旧段无法归档就不能删。排查方式:

    # 查看归档统计,failed_count 持续增长就是归档卡住
    psql -U postgres -c "SELECT * FROM pg_stat_archiver;"
    
    

    查看 pg_wal 下的 .ready 文件,这些是等待归档的段

    ls $PGDATA/pg_wal/*.ready 2>/dev/null | head

    手动跑一次归档命令看报错(把 %p %f 换成真实路径测试)

    具体命令以 archive_command 配置为准

    归档失败常见原因有:目标目录权限不对、远程存储不可写、脚本里路径写错、备份盘满。修好之后归档会继续推进,.ready 文件消失,WAL 才开始回收。

    三、把监控和清理做成常态

    临时救火只能解一次,长期还是靠监控和规范。建议至少做三件事。

    一是给复制槽做定时巡检,把不活跃且保留量大的槽告警出来:

    # 放进巡检脚本,输出保留 WAL 超过 1GB 的槽
    psql -U postgres -At -c "SELECT slot_name || ' ' ||\n  pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn))\n  FROM pg_replication_slots\n  WHERE pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) > 1073741824;"

    二是给 pg_wal 所在分区单独做磁盘水位监控,80% 就要留意,90% 必须处理,别等到写入被拒。三是给逻辑复制的创建和销毁定流程:删除订阅时顺手删对应槽,从库下线时同步清理槽,避免留下“孤儿槽”。

    另外,从库长时间停机前,要么先停复制并删除槽,要么接受它可能追不上而重建;如果业务对 WAL 保留很敏感,可以考虑在从库侧做合理的恢复配置,具体参数以官方文档为准。

    小结

    pg_wal 被撑满导致数据库拒绝写入,多数时候根子在复制槽没释放或归档卡住,而不是 WAL 真的生成太多。排查顺序是:先看 pg_replication_slots 里谁保留了 WAL、是否 active,再看 pg_stat_archiver 有没有归档失败,确认消费者确实废弃后再删槽。删槽前一定确认没有活跃消费端,删完观察一轮 checkpoint 后空间是否回落。把这几个查询做成巡检项,能省下不少半夜救火的时间。

    继续阅读

    📑 📅
    Docker 挂载 NFS 卷卡死排查:stat、df 与 nfsstat 定位失联 2026-09-26
    Linux服务器内存泄漏初判:RSS上涨是缓存还是泄漏 2026-09-26
    MySQL死锁日志定位实战:读懂SHOW ENGINE INNODB STATUS 2026-09-26
    Nginx 上游节点健康检查实战:max_fails、fail_timeout 与 backup 配置 2026-09-26
    MySQL主从复制中断后如何安全恢复 2026-09-25
    宿主机与容器时间不一致导致接口签名失败排查 2026-09-25
    Nginx与上游服务时间不同步导致签名校验失败排查 2026-09-25
    Nginx proxy_pass 带 URI 与不带 URI:路径拼接错乱排查 2026-09-27
    Docker 容器 PID 1 与信号处理:docker stop 超时强制 kill 的成因与写法 2026-09-27
    PostgreSQL连接池选型与pgbouncer事务池踩坑 2026-09-27