拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PG 孤儿物理复制槽残留导致 pg_wal 膨胀 19G 故障分析

PG 孤儿物理复制槽残留导致 pg_wal 膨胀 19G 故障分析

文章目录

  • PG 孤儿物理复制槽残留导致 pg_wal 膨胀 19G 故障分析
    • 1. 故障现象
      • 1.1 磁盘空间异常
      • 1.2 复制槽查询发现异常
    • 2. 排查过程
      • 2.1 关键指标解读
      • 2.2 排除其他可能
      • 2.3 复制槽基础原理(排查依据)
    • 3. 根因分析
      • 3.1 事故链
      • 3.2 缺陷定性
      • 3.3 三个放大隐患
    • 4. 解决方案
      • 4.1 删除前安全检查(必做)
      • 4.2 执行删除
      • 4.3 验证结果
      • 4.4 若需主动加速回收
    • 5. 经验总结
      • 5.1 排查思路(可复用)
      • 5.2 常用运维命令
      • 5.3 预防措施
      • 5.4 关键结论

PG 孤儿物理复制槽残留导致 pg_wal 膨胀 19G 故障分析

项目内容
故障日期2026-09-17
故障现象服务器磁盘被pg_wal占用19G(正常应为几十 MB),存在撑满磁盘、拖死主库的风险
影响范围停车场环境主库所在服务器;pg_wal持续膨胀会最终写满磁盘导致数据库不可用
根因配置 2 个从机时底层在主库创建了 2 个物理复制槽(命名含从机 IP);解除主从关系时未删除槽,叠加环境从 192 网段切换到 172 网段,旧槽再无消费者,成为孤儿槽并强制保留 WAL
解决方案确认无消费者后,用pg_drop_replication_slot()删除 2 个孤儿槽,pg_wal由 19G 降至 17M
环境信息openEuler + PostgreSQL 15.18(PolarDB 15.18.5.0 内核);PGDATA:/postgresql/pgdata

1. 故障现象

1.1 磁盘空间异常

pg_wal目录占用异常,达 19G:

[root@openEuler ~]# du -sh /postgresql/pgdata/pg_wal19G /postgresql/pgdata/pg_wal[root@openEuler ~]# du -sh /postgresql/pgdata/1.7G /postgresql/pgdata/# 删除前 pgdata 总量被 pg_wal 占据

1.2 复制槽查询发现异常

postgres=# select * from pg_replication_slots;slot_name|plugin|slot_type|datoid|database|temporary|active|active_pid|xmin|catalog_xmin|restart_lsn|confirmed_flush_lsn|wal_status|safe_wal_size|two_phase------------------------------------+--------+-----------+--------+----------+-----------+--------+------------+------+--------------+-------------+---------------------+------------+---------------+-----------repl_replication_slot_192_168_0_96||physical|||f|f||||0/C37EFE8||extended||f repl_replication_slot_192_168_0_99||physical|||f|f||||0/C350168||extended||f repl_replication_slot_172_168_0_99||physical|||f|t|1009698|||C/3C29A000||reserved||f repl_replication_slot_172_168_0_96||physical|||f|t|1009542|||C/3C29A000||reserved||f(4rows)

4 个物理复制槽分成泾渭分明的两组:

槽名activerestart_lsnwal_status判定
repl_replication_slot_192_168_0_96f0/C37EFE8extended孤儿槽(192 旧网段)
repl_replication_slot_192_168_0_99f0/C350168extended孤儿槽(192 旧网段)
repl_replication_slot_172_168_0_99tC/3C29A000reserved正常在用(172 现网段)
repl_replication_slot_172_168_0_96tC/3C29A000reserved正常在用(172 现网段)

2. 排查过程

2.1 关键指标解读

(1)**active**字段——判断槽有没有消费者

active表示该槽当前是否正被 walsender 进程占用(即active_pid IS NOT NULL)。192 的两个槽active=f且active_pid为空,说明没有任何复制连接在消费它们。

(2)**restart_lsn**字段——判断槽是否"冻结"

  • 192 槽的restart_lsn停留在0/...(逻辑段号 0)

  • 172 槽的restart_lsn已在C/...(逻辑段号 12)

  • 1 个 WAL 逻辑段 = 256 个 16MB 段文件 ≈ 4GB

两者相差多个逻辑段,说明 192 这两个槽从极早的位置起就再没被消费过,完全对应"环境从 192 网段切到 172 网段后就没人认领了"。

(3)**wal_status**字段——WAL 膨胀的直接铁证

PG 13+ 引入该字段,取值含义:

取值含义
reservedWAL 保留量在max_wal_size范围内(正常)
**extended**保留量已超出**max_wal_size**,但因槽存在被迫继续保留
unreserved不再保留 WAL,restart_lsn之前的 WAL 可能已被删除
lost槽已失效,需要重建

192 槽是extended——这就是**pg_wal**被撑到 19G 的直接原因;而 172 槽是reserved,属正常范围。

2.2 排除其他可能

排查项结果结论
是否有 walsender 连接 192 槽active=f、active_pid为空无消费者,可安全删除
172 槽是否正常active=t,restart_lsn持续推进现网段从机在用,不可动
项目打包目录是否有槽管理代码product/Linux/x86_64/{bin,script}搜索repl_replication_slot/physical_replication_slot/pg_drop_replication_slot均无命中槽由底层业务代码运行时动态创建,不经postgresql.conf模板

2.3 复制槽基础原理(排查依据)

复制槽是什么?

复制槽(replication slot)是主库上的持久化对象,作用是"向主库承诺保留从该位置起的 WAL,直到消费者确认已接收"。

  • 存储位置:$PGDATA/pg_replslot/<slot_name>/state

  • 生命周期:显式创建(pg_create_physical_replication_slot()或pg_basebackup -S ... -C),只有显式**pg_drop_replication_slot()**才会删除

  • PG 没有任何"备库不连了就自动删槽"的机制

为什么槽会导致 WAL 堆积?

槽存在 → 主库必须保留 restart_lsn 之后的所有 WAL ↓ 备库不再消费 → restart_lsn 不再推进 ↓ WAL 无限累积 → pg_wal 持续膨胀 → 磁盘写满 → 主库挂掉

两个易混淆视图的区别:

pg_stat_replicationpg_replication_slots
反映什么当前活跃的 walsender 连接(会话级)已创建的复制槽(持久化对象)
何时有记录有人连上来做流复制就有只有显式创建过槽才有
何时消失备库断开即消失只有 drop 才消失
无槽流复制有记录空

注:无槽流复制(未配primary_slot_name)虽然也能跑通,但没有 WAL 保留保障,仅靠wal_keep_size(默认 0)兜底,主库 checkpoint 后可能回收掉备库未接收的 WAL,导致备库报requested WAL segment ... has already been removed而断流。


3. 根因分析

3.1 事故链

1. 停车场环境配置 2 个从机 2. 底层业务代码在主库创建 2 个物理复制槽 命名规则:repl_replication_slot_<从机IP下划线形式> → repl_replication_slot_192_168_0_96 / repl_replication_slot_192_168_0_99 3. 环境从 192 网段切换到 172 网段,新建 2 个槽 → repl_replication_slot_172_168_0_96 / repl_replication_slot_172_168_0_99 4. 解除原主从关系时,未执行 pg_drop_replication_slot() 5. 192 两个槽失去消费者 → active=f,restart_lsn 永久停滞 6. PG 仍按承诺强制保留 WAL → wal_status=extended(超出 max_wal_size) 7. WAL 持续累积 → pg_wal 膨胀至 19G

3.2 缺陷定性

资源创建与释放不对称:底层创建了持久化对象(复制槽),解除主从关系时没有对应的销毁动作。这是代码缺陷,而非运维误操作。

3.3 三个放大隐患

隐患说明后果
槽名内嵌 IP命名规则repl_replication_slot_<IP>把网络地址编进了对象名换网段/改 IP 后旧槽名无法被自动识别清理,必然残留
备库侧**primary_slot_name**未清理若只删了主库的槽、没清备库postgresql.auto.conf中的primary_slot_name该实例下次作为备库启动会报replication slot "xxx" does not exist,挂不上复制
无监控告警孤儿槽从产生到磁盘被撑到 19G 期间零告警只能被动发现,等到磁盘告警才暴露

4. 解决方案

4.1 删除前安全检查(必做)

-- 1) 确认目标槽无消费者SELECTslot_name,slot_type,active,active_pid,restart_lsn,pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(),restart_lsn))ASretained_walFROMpg_replication_slotsWHEREslot_nameIN('repl_replication_slot_192_168_0_96','repl_replication_slot_192_168_0_99');-- 2) 对照当前实际连接SELECTapplication_name,client_addr,state,sync_stateFROMpg_stat_replication;

判定标准:active = f且active_pid为空,pg_stat_replication中无对应连接 → 可安全删除。

若槽为active = t,pg_drop_replication_slot()会直接报错replication slot "xxx" is active for PID nnn,不会误删。

4.2 执行删除

SELECTpg_drop_replication_slot('repl_replication_slot_192_168_0_96');SELECTpg_drop_replication_slot('repl_replication_slot_192_168_0_99');

4.3 验证结果

删除后仅剩 2 个在用的槽:

postgres=# select * from pg_replication_slots;slot_name|plugin|slot_type|datoid|database|temporary|active|active_pid|restart_lsn|wal_status|two_phase------------------------------------+--------+-----------+--------+----------+-----------+--------+------------+-------------+------------+-----------repl_replication_slot_172_168_0_99||physical|||f|t|1009698|C/3C3C8000|reserved|f repl_replication_slot_172_168_0_96||physical|||f|t|1009542|C/3C3C8000|reserved|f(2rows)

注意:172 槽的restart_lsn由C/3C29A000推进到C/3C3C8000,确认活跃槽工作正常。

**pg_wal**空间快速回落:

[root@openEuler ~]# du -sm /postgresql/pgdata/pg_wal/17265→15873→15457→6529→6465→6177→3921→3217→2673→1377→17# 数十秒内由 17GB 降至 17MB[root@openEuler ~]# du -sh /postgresql/pgdata/pg_wal/17M /postgresql/pgdata/pg_wal/[root@openEuler ~]# du -sh /postgresql/pgdata/1.7G /postgresql/pgdata/

回收为何这么快?

PG 在pg_drop_replication_slot()之后会主动触发一次 checkpoint(CHECKPOINT_IMMEDIATE | FORCE | WAIT),因此 WAL 回收立即启动,无需等待常规checkpoint_timeout周期。

执行du期间出现du: cannot access '.../0000000100000008000000CA': No such file or directory属正常现象——是du扫描目录时 WAL 文件正被 unlink。

4.4 若需主动加速回收

CHECKPOINT;-- 手动触发回收SELECTcount(*)FROMpg_ls_waldir();-- 观察 WAL 文件数量

若删槽 + checkpoint 后pg_wal仍不下降,需排查另一条链路:

SHOWarchive_mode;-- on 且归档积压时 WAL 也不会删SELECT*FROMpg_stat_archiver;

5. 经验总结

5.1 排查思路(可复用)

  1. **pg_wal**异常膨胀,先查复制槽:SELECT * FROM pg_replication_slots;——槽是 WAL 堆积的头号嫌疑

  2. 看**active**分组:active=f的是孤儿槽,active=t的是在用槽

  3. 看**wal_status**定性:extended= 已被迫超量保留 WAL,是膨胀的直接证据

  4. 看**restart_lsn**是否推进:停滞说明消费者已消失

  5. 用**pg_stat_replication**交叉验证:为空即无 walsender 接入,可放心清理

  6. 删槽前必须确认无消费者,删除后 PG 会自动 checkpoint 回收

5.2 常用运维命令

-- 查看所有槽及关键状态SELECTslot_name,slot_type,active,active_pid,restart_lsn,wal_status,pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(),restart_lsn))ASretained_walFROMpg_replication_slotsORDERBYactive;-- 查看当前活跃复制连接SELECTapplication_name,client_addr,state,sync_state,sent_lsn,replay_lsnFROMpg_stat_replication;-- 查看 WAL 总量与文件数SELECTpg_size_pretty(sum(size))FROMpg_ls_waldir();SELECTcount(*)FROMpg_ls_waldir();-- 幂等清理(按前缀批量删,避免单个槽不存在导致流程中断)SELECTpg_drop_replication_slot(slot_name)FROMpg_replication_slotsWHEREslot_nameLIKE'repl_replication_slot_%'ANDactive=false;-- 触发回收CHECKPOINT;
# 查看 pg_wal 占用du-sh$PGDATA/pg_wal

5.3 预防措施

层次措施说明
代码修复(根本)解除主从关系时同步pg_drop_replication_slot()资源创建/释放必须对称,建议提问题单推动底层修复
命名优化槽名不要内嵌 IP,改用稳定标识(从机序号/UUID)换网段/改 IP 后不会产生"无法识别的孤儿槽"
清理幂等清理逻辑按条件删(WHERE slot_name LIKE ... AND active = false)pg_drop_replication_slot()对不存在的槽会报错,需容错
同步清理备库配置解除主从时一并清理备库primary_slot_name否则该实例下次作备库会报replication slot ... does not exist
监控告警① 孤儿槽:active=f且restart_lsn停滞超 N 小时
②wal_status = extended即告警
③pg_wal目录容量
本次从产生到 19G 全程零告警
防御性配置评估设置max_slot_wal_keep_size(默认-1不限制)给单个槽的 WAL 保留量设上限,防止单个孤儿槽撑爆磁盘
流程固化解除主从标准清单:删槽 → 清备库primary_slot_name→ 清standby.signal→CHECKPOINT回收避免遗漏

5.4 关键结论

复制槽是"向主库承诺保留 WAL"的持久化对象,PG 不会自动清理。只要槽失去消费者又不删除,它就会持续拽住 WAL 不放,wal_status会变成extended并最终撑满磁盘。 因此:凡是有槽的实例,必须建立孤儿槽巡检机制;凡是有槽创建逻辑的代码,必须有对应的删除逻辑。

返回列表