PostgreSQL WAL 排障实战(第 11 篇):主从延迟为零,闲置逻辑槽为什么仍能撑爆 pg_wal
2026/9/19 23:29:13 网站建设 项目流程

物理副本监控显示延迟为零,读流量也正常,主库pg_wal却每天增长数百 GB。直接答案是:物理副本追平只覆盖一条物理复制链;三周前停用的 CDC 逻辑槽虽然没有连接,仍可能用旧restart_lsn抬高 WAL 回收边界。

PostgreSQL 没有一个“复制延迟”数字能覆盖所有消费者。物理副本追平,只能证明那条复制链;逻辑槽是否确认消费,要看自己的confirmed_flush_lsnrestart_lsn

一次看全物理链与所有槽

物理/流复制连接:

SELECTapplication_name,client_addr,state,sync_state,sent_lsn,write_lsn,flush_lsn,replay_lsn,pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(),replay_lsn))ASreplay_gapFROMpg_stat_replication;

全部复制槽:

SELECTslot_name,slot_type,database,active,active_pid,restart_lsn,confirmed_flush_lsn,inactive_since,wal_status,pg_size_pretty(safe_wal_size)ASsafe_wal_size,invalidation_reason,pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(),restart_lsn))ASretained_gapFROMpg_replication_slotsORDERBYrestart_lsn NULLSLAST;

这些位点回答不同问题:

  • sent_lsn:主库 WAL sender 已发送到哪里;
  • write_lsn:接收端写入操作系统的位置;
  • flush_lsn:接收端确认持久化的位置;
  • replay_lsn:物理副本已经重放的位置;
  • confirmed_flush_lsn:逻辑消费者已确认接收的位置;
  • restart_lsn:该槽仍可能需要的最老 WAL,因此是保留边界。

active = false只表示当前没有流式消费者,不表示槽不保留 WAL。replay_lag = 0也可能在副本空闲、统计语义或采样时点下被误读,应同时看 LSN 字节差和时间序列。

再把逻辑位点与实际 WAL 目录、归档状态交叉验证:

SELECTcount(*)ASwal_files,pg_size_pretty(sum(size))ASwal_dir_size,min(modification)ASoldest_file_time,max(modification)ASnewest_file_timeFROMpg_ls_waldir();SELECTarchived_count,failed_count,last_archived_wal,last_archived_time,last_failed_wal,last_failed_timeFROMpg_stat_archiver;

pg_ls_waldir()读取服务器目录清单,需要受控的高权限,不能向普通业务账号开放;它反映当前目录实际占用,却不能说明每个文件由哪个槽保留。pg_stat_archiver能发现归档失败这条替代假设,但累计计数可能跨越很长时间,判断时要看最近失败时间和监控增量。

LSN retained gap 上升 + pg_wal 同步增长 → 支持 slot 保留假设 slot gap 稳定 + last_failed_time 持续刷新 → 优先排查归档失败 两者同时异常 → 不能只处理其中一条容量链

测试槽复现 WAL 保留

仅在隔离测试实例执行。前提是wal_level = logical、存在足够槽位,并允许使用 PostgreSQL 自带test_decoding输出插件。

SHOWwal_level;SHOWmax_replication_slots;SELECT*FROMpg_create_logical_replication_slot('demo_idle_slot','test_decoding');CREATETABLEwal_demo(idbigint,payloadtext);INSERTINTOwal_demoSELECTg,repeat(md5(g::text),32)FROMgenerate_series(1,200000)ASg;SELECTslot_name,active,restart_lsn,confirmed_flush_lsn,pg_wal_lsn_diff(pg_current_wal_lsn(),restart_lsn)ASretained_bytes,wal_status,safe_wal_sizeFROMpg_replication_slotsWHEREslot_name='demo_idle_slot';

消费者从未读取和确认,数据库继续生成 WAL,current_lsn - restart_lsn会增长。实际pg_wal目录大小受 checkpoint、段回收、归档和其他槽共同影响,所以本实验用位点差证明保留责任,不承诺文件尺寸与差值逐字节相等。

测试结束,先确认名称只属于本实验:

SELECTslot_name,slot_type,database,activeFROMpg_replication_slotsWHEREslot_name='demo_idle_slot';SELECTpg_drop_replication_slot('demo_idle_slot');DROPTABLEIFEXISTSwal_demo;

该删除只因槽由本实验创建且没有业务消费者。生产槽绝不能照抄:位点丢失后,CDC 可能无法连续恢复,必须重新快照并做全量对账。

max_wal_size为什么挡不住

max_wal_size是 checkpoint 频率相关的软阈值,不是pg_wal硬容量上限。以下因素都可能要求保留更多 WAL:

  • replication slot 的restart_lsn
  • WAL 归档持续失败;
  • wal_keep_size保留下限;
  • checkpoint 周期和高峰生成量;
  • 备份或恢复链上的其他需要。

PostgreSQL 18 的max_slot_wal_keep_size可以限制槽在 checkpoint 时允许保留的 WAL。默认-1表示无限制。超过有限上限后,槽所需 WAL 可能被移除,最终wal_status = lost,消费者不能继续。

这不是无损容量保护,而是明确取舍:保护主库磁盘,必要时牺牲消费者连续性。

safe_wal_size表示在槽有丢失危险前还能写多少 WAL;无限制或槽已 lost 时为 NULL。应按其下降速度告警,而不是等磁盘 95% 才处理。

源码证明:旧restart_lsn怎样挡住 WAL 回收

PostgreSQL 18.6 的保留链跨两个模块,不是pg_replication_slots视图自己“保存文件”:

各 slot.restart_lsn → ReplicationSlotsComputeRequiredLSN() → XLogCtl.replicationSlotMinLSN → xlog.c / KeepLogSeg() → checkpoint 计算最早可删除的 WAL segment

src/backend/access/transam/xlog.cKeepLogSeg()先通过XLogGetReplicationSlotMinimumLSN()取得所有槽要求的最老 LSN,并据此向前移动保留边界;配置了非负max_slot_wal_keep_size时,它又把槽允许保留的距离限制在该值内。随后它还会合并 WAL summarization 与wal_keep_size的要求,所以“某个槽的 retained gap”与最终目录尺寸不是一一相等关系。

当 checkpoint 推进到需要移除槽所需 WAL 时,src/backend/replication/slot.cDetermineSlotInvalidationCause()会比较restart_lsn与最老可用 LSN:前者更旧时返回RS_INVAL_WAL_REMOVEDInvalidateObsoleteReplicationSlots()标记槽失效并重新计算资源下限,视图最终表现为invalidation_reason = 'wal_removed',槽可能进入lost

源码状态/分支SQL 证据运维含义
replicationSlotMinLSN长期不前进最老restart_lsn与 retained gap 持续增长某个槽正在抬高回收边界
max_slot_wal_keep_size >= 0限制保留距离safe_wal_size下降,wal_status由 reserved/extended 变化主库容量保护正在逼近消费者连续性代价
RS_INVAL_WAL_REMOVEDwal_status = 'lost'invalidation_reason = 'wal_removed'所需 WAL 已丢,不能靠重新连接续传
RS_INVAL_IDLE_TIMEOUTinvalidation_reason = 'idle_timeout'checkpoint 已使超时闲置槽失效

本文不使用 Java 客户端作为核心证据,因为 WAL 文件回收和槽失效由服务端 checkpoint/slot 状态机决定;只读系统视图、固定版本源码和隔离 SQL 实验比任意 CDC SDK 更直接。

复制槽不只可能保留 WAL,逻辑槽的catalog_xmin还可能影响系统目录版本回收;这条清理链及其与只读长事务的区别见第 8 篇:没有锁等待,只读事务为什么仍能拖胖整库。

闲置槽超时也会让消费者失效

idle_replication_slot_timeout可使长期 inactive 的槽在 checkpoint 时失效。超时到点不一定立即发生,因为失效在 checkpoint 处理;强制 checkpoint 只为立刻失效槽会引入 I/O,不应当作日常操作。

槽因超时失效后,invalidation_reason = idle_timeout。它仍需要消费者重建方案、负责人和告警,不能把超时当自动垃圾桶。

三类“正常”指标为何会误导

正常指标它实际证明它漏掉什么
物理replay_gap = 0某条物理副本已重放到当前附近逻辑槽、归档和其他副本
slotactive = true有进程正在使用槽消费速度是否追上 WAL 生成
CDC 作业 RUNNING进程/任务状态正常是否确认位点、下游是否落库正确
max_wal_size未改checkpoint 目标没变槽可无限保留 WAL

技术位点还必须与业务结果闭环:目标端最大业务版本、端到端延迟、重复/丢失和重建能力。confirmed_flush_lsn前进不等于下游业务表一定正确。

生产处置:容量告警时先保护证据

只读确认

  1. 记录文件系统剩余量、WAL 每分钟增长与预计耗尽时间。
  2. 列出所有槽的负责人、active、restart/confirmed、status 和 retained gap。
  3. 检查物理复制、归档状态、备份与当前 WAL 生成速率。
  4. 向 CDC 团队确认最后成功业务位点及从哪里可重新快照。

最小止血

先暂停非必要高 WAL 批作业、扩容或释放已确认无关的文件,争取决策时间。不要手工删除pg_wal文件;这会破坏恢复与复制,可能使实例无法启动。

根因修复

  • 每个 slot 绑定负责人、用途、消费 SLA、容量预算和失效策略;
  • 对停用 CDC 走“停止生产 → 记录最终位点 → 下游验收 → 删除槽”流程;
  • 根据磁盘和重建 RTO 评估max_slot_wal_keep_size
  • 只有具备重建能力的槽才启用合理 idle timeout;
  • 同时监控 retained bytes、safe WAL、磁盘耗尽时间和业务端到端延迟。

删除生产槽的护栏

删除前必须有:槽的精确名称与数据库、消费者书面确认、最后业务位点、可用源端快照/备份、重建步骤和对账口径。先处理一个已确认废弃槽,观察磁盘、checkpoint、归档和其他消费者。

若消费者身份不明、仍可能恢复、快照能力未验证或业务对账失败,立即停止。删除槽不可通过重新创建同名槽恢复原 LSN;回滚本质是重新建立一致性,而不是撤销 SQL。

证据边界

证据能证明不能证明
restart_lsn很旧槽保留较老 WAL消费者为何停滞
confirmed_flush_lsn前进消费者确认了逻辑流下游业务写入正确
wal_status = lost槽已不可继续使用所需 WAL自动完成消费者重建
删除槽后磁盘下降该槽是重要保留因素所有 WAL 风险已消失
物理副本追平该物理链路正常所有逻辑消费者正常

面试表达主线

物理复制看 sent/write/flush/replay,逻辑槽看 confirmed/restart;restart_lsn决定仍可能保留的最老 WAL。max_wal_size不是硬上限,max_slot_wal_keep_size通过允许槽失效保护磁盘。每个槽都必须有负责人、容量上限、告警和重建对账方案。

实验清理核对

测试结束后应返回 0 行:

SELECTslot_nameFROMpg_replication_slotsWHEREslot_name='demo_idle_slot';

若仍存在,不要反复执行删除;先确认是否有其他人复用了同名槽,以及active_pid指向哪个会话。

官方资料

  • PostgreSQL 18:Replication Slots
  • PostgreSQL 18:pg_replication_slots
  • PostgreSQL 18:Replication Configuration
  • PostgreSQL 18:WAL Configuration
  • PostgreSQL 18:System Administration Functions
  • PostgreSQL 18:pg_stat_archiver
  • PostgreSQL 18.6 源码:xlog.c / KeepLogSeg()
  • PostgreSQL 18.6 源码:slot.c / 槽失效判断
  • PostgreSQL 18.6 源码标签 REL_18_6

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

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

立即咨询