Skip to content

复制槽(Replication Slot) ​

复制槽(replication slot)是 PostgreSQL 提供的一种“让主库记住从库进度”的机制。在它出现之前,主库并不知道某个从库到底消费到了哪一段 WAL,只能靠 wal_keep_size 多留一些 WAL 段碰运气;一旦从库掉线时间过长、WAL 被 checkpoint 回收掉,从库就只能重新做一次基础备份。复制槽把这个“保留点”变成了主库中的一条持久化记录。

从备份与恢复的角度看,复制槽有两层意义。第一层是它与 WAL 保留直接相关:一个长期不活跃的物理槽会一直拽住 WAL,导致 pg_wal 目录无限增长,最终写满磁盘;反过来,如果给 max_slot_wal_keep_size 设了值,槽可能会因为 WAL 被回收而“失效”(lost),此时依赖它的从库必须重建。第二层是它与 PITR 的关系:复制槽本身不是备份,它不保存数据文件,只保存一个 WAL 位置,所以它不能替代归档,也不能替代基础备份。

本篇先讲清楚复制槽的两种类型与关键字段,再说明它与 max_slot_wal_keep_size、wal_status、失效原因之间的联动,最后给出可落地的监控与清理实践。重点始终放在一个问题上:这个槽会不会把 WAL 撑爆,或者因为 WAL 被回收而失效。

前置知识

建议先阅读:按时间 / LSN / 事务 ID 恢复

基础概念 ​

复制槽解决什么问题 ​

在没有复制槽的年代,主库与从库之间的进度是“无状态”的:从库连上主库,告诉主库“我要从 LSN X 开始拉数据”;主库照做,但不记录这个从库的任何信息。于是会出现两种情况:

  • 从库掉线一天后回来,它要的那段 WAL 早被 checkpoint 回收了,重连直接失败,只能重建。
  • 主库为了让从库能追上,只能盲目调大 wal_keep_size,留一堆可能没人要的 WAL 占磁盘。

复制槽把“某个消费者还欠多少 WAL”变成主库里的持久化状态。主库在 checkpoint 时会检查所有槽的 restart_lsn,凡是仍然需要的 WAL 段都不回收。代价是:责任从“多留一点”变成了“必须有人消费”。

物理槽与逻辑槽 ​

类型用途关联数据库保留什么
物理槽(physical)流复制、pg_receivewal 等基于 WAL 的消费无从 restart_lsn 起的整段 WAL
逻辑槽(logical)逻辑解码、逻辑复制、CDC有(datoid / database)从 restart_lsn 起、且满足解码所需的 WAL;同时用 xmin 拽住旧行版本

物理槽只关心“WAL 文件别删”,逻辑槽还要关心“表里的旧行版本别被 VACUUM 清掉”,所以逻辑槽多出 xmin 与 catalog_xmin 两列。这两种影响是独立叠加的:一个卡住的逻辑槽既会让 pg_wal 膨胀,也会让表和系统表膨胀。

关键字段:restart_lsn 与 confirmed_flush_lsn ​

  • restart_lsn:该槽可能仍然需要的最老 WAL 位置。checkpoint 不会回收这个位置之后的 WAL。如果没有被保留过任何 WAL,该列为 NULL。
  • confirmed_flush_lsn:逻辑槽的消费者已确认收到的位置。这个位置之前的事务数据不再可用。物理槽该列为 NULL。

两者不要混用。restart_lsn 是“主库承诺不删到哪”,confirmed_flush_lsn 是“消费者确认到哪”。前者决定磁盘占用,后者决定逻辑解码能看到什么。

wal_status:四种取值 ​

pg_replication_slots.wal_status 描述这个槽所需的 WAL 文件是否还在,取值只有四种:

取值含义
reserved所需 WAL 在 max_wal_size 范围内,状态正常
extended超出了 max_wal_size,但文件仍被保留(被本槽或 wal_keep_size 留住)
unreserved槽已不再保留所需 WAL,其中一部分将在下次 checkpoint 被删除;通常出现在 max_slot_wal_keep_size 为非负值时。该状态可能回到 reserved 或 extended
lost槽已不可用(WAL 已被回收)

lost 是终局状态,不会再回到正常。从 unreserved 掉到 lost 中间可能只有一次 checkpoint 的时间窗口,因此对 unreserved 的告警必须足够灵敏。

safe_wal_size:还剩多少余量 ​

safe_wal_size 表示“还能再写入多少字节的 WAL,而不至于让这个槽变成 lost”。它的取值规则需要特别注意:

  • 对于已经 lost 的槽,该列为 NULL。
  • 当 max_slot_wal_keep_size = -1(默认,表示不限制)时,该列也为 NULL。
  • 其他情况下,它给出一个正整数字节数,是非常实用的容量预警指标。

因此如果 safe_wal_size 是 NULL,先确认是不是没配 max_slot_wal_keep_size,而不是直接认为“安全”。

相关参数 ​

参数默认值作用生效方式
max_replication_slots10服务端最多支持多少个槽仅 server start
max_slot_wal_keep_size-1(不限制)槽最多能拽住多少 MB 的 WAL仅 postgresql.conf / 命令行
wal_keep_size0为非槽消费者保底保留的 WAL 大小仅 postgresql.conf / 命令行
wal_levelreplica必须为 replica 或更高才能使用槽仅 server start
idle_replication_slot_timeout0(禁用)超过此时长未使用的槽会被失效处理仅 postgresql.conf / 命令行

max_replication_slots 调小于当前已有槽数量会导致服务端无法启动,这一点在缩容时极易踩坑。

inactive_since 与 conflicting ​

inactive_since 记录槽变为不活跃的时刻;若槽正在被消费则为 NULL。该列由较新版本引入,用于配合 idle_replication_slot_timeout 判断空闲时长。注意:槽一旦失效,该值不再更新。

conflicting 是 PostgreSQL 16 引入的列,表示逻辑槽是否因为在恢复期间发生冲突而失效。该列为 true 时应查看 invalidation_reason。物理槽该列恒为 NULL。

invalidation_reason:失效原因 ​

槽失效后,invalidation_reason 会给出原因:

取值含义适用类型
wal_removed所需的 WAL 已被删除物理槽与逻辑槽
rows_removed所需的行已被删除(VACUUM 清掉了旧版本)仅逻辑槽
wal_level_insufficient主库的 wal_level 不足以做逻辑解码仅逻辑槽
idle_timeout空闲时间超过 idle_replication_slot_timeout物理槽与逻辑槽

看到原因之后应立刻判断修复路径:wal_removed 基本只能重建消费者;rows_removed 只能重新初始化逻辑复制;wal_level_insufficient 需要改配置重启;idle_timeout 需要确认是业务下线还是意外掉线。

临时槽与永久槽 ​

槽可以是临时的(temporary 列为 true),临时槽不落盘,会话结束或出错时自动删除。pg_basebackup 在流式备份时会按需使用临时槽,因此不会在备份结束后留下垃圾。

永久槽(默认)会写入 pg_replslot 目录,重启后依然存在。这正是风险来源:一个被遗忘的永久槽会永久拽住 WAL。

语法与用法 ​

查看现有槽 ​

sql
SELECT slot_name,
       slot_type,
       database,
       active,
       active_pid,
       restart_lsn,
       confirmed_flush_lsn,
       wal_status,
       safe_wal_size,
       invalidation_reason
FROM pg_replication_slots
ORDER BY slot_type, slot_name;

创建物理槽 ​

sql
SELECT * FROM pg_create_physical_replication_slot('standby_a');

在从库的 primary_conninfo 中指定 primary_slot_name = 'standby_a' 即可使用该槽。

创建逻辑槽 ​

sql
SELECT * FROM pg_create_logical_replication_slot('cdc_slot', 'pgoutput');

第一个参数是槽名,第二个参数是输出插件名。插件必须是已安装且被信任的(见 output_plugin_libraries)。

删除槽 ​

sql
SELECT pg_drop_replication_slot('standby_a');

如果槽仍处于活跃状态,删除会失败。此时应先停掉消费者,或用 pg_terminate_backend(active_pid) 终止对应会话(需谨慎)。

推进槽位置 ​

sql
SELECT * FROM pg_replication_slot_advance('standby_a', '0/1A2B3C48');

该函数把槽的确认位置向前推进,且不会后退,也不会超过当前插入位置。返回槽名与推进到的实际位置。注意:更新后的位置信息在下次 checkpoint 时才落盘,因此崩溃后槽可能回退到较早的位置。

查看槽对应的 WAL 余量 ​

sql
SELECT slot_name,
       wal_status,
       pg_size_pretty(safe_wal_size::numeric) AS safe_remaining
FROM pg_replication_slots;

计算槽滞后多少 ​

sql
SELECT slot_name,
       pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS lag_bytes
FROM pg_replication_slots
WHERE restart_lsn IS NOT NULL;

pg_wal_lsn_diff 用当前 WAL 位置减去槽的 restart_lsn,得到字节差,可用于告警阈值。

详细说明 ​

为什么复制槽会撑爆 pg_wal ​

槽的核心承诺是“restart_lsn 之后的 WAL 不删”。一旦消费者停止推进,restart_lsn 就冻结在原地,而业务仍在持续写 WAL。磁盘会以业务写入速率持续增长,直到 pg_wal 所在文件系统写满。

写满的后果非常严重:PostgreSQL 无法写入 WAL 时会导致 PANIC 级别的关闭,而不是温和地拒绝写入。这不是“慢一点”的问题,而是整个实例停摆。因此给槽配监控、给 pg_wal 配独立磁盘并预留足够空间是上线前的硬要求。

max_slot_wal_keep_size 的取舍 ​

max_slot_wal_keep_size = -1(默认)表示不限制,槽可以无限拽住 WAL——安全的是从库,危险的是主库磁盘。

设成一个有限值(例如 max_slot_wal_keep_size = '10GB')后,语义变成:当某个槽的 restart_lsn 落后当前 LSN 超过该值时,主库会放弃这个槽的 WAL 保留,把 WAL 删掉以保护主库。代价是槽可能变成 lost,从库必须重建。

这是一个明确的策略选择,没有两全:

  • 保主库:设有限值,牺牲从库可用性。适合从库可以重建、主库磁盘有限的场景。
  • 保从库:保持 -1,靠监控和人工干预。适合从库重建代价极高(数据量大、带宽有限)的场景。

无论选哪种,都必须配合监控。

wal_status 与 safe_wal_size 的监控价值 ​

wal_status 从 reserved 到 extended 再到 unreserved,是一条清晰的退化路径。最实用的做法是:

  • 对 unreserved 立即告警,因为下一次 checkpoint 就可能变成 lost。
  • 监控 safe_wal_size,在剩余量低于某个绝对值(例如 5GB)时告警。
  • 对 lost 告警,并触发“从库需要重建”的处置流程。

只监控 active = false 是不够的:一个槽可能 active = true 但消费极慢,restart_lsn 一直在落后,同样会撑爆 WAL。

槽与 PITR 归档的关系 ​

必须区分两条 WAL 保留链路:

  • 槽链路:pg_wal 目录里的 WAL,是否被消费;由 restart_lsn 与 wal_keep_size 决定。
  • 归档链路:WAL 是否被 archive_command / archive_library 复制到归档目录;由 archive_mode 与归档进程决定。

这两条链路互不替代。槽能让从库追上,但槽被删或槽失效后,pg_wal 里的 WAL 照样会被回收;只有归档里的 WAL 才能用于未来的 PITR。反过来,归档正常也不能阻止 pg_wal 因槽而膨胀,因为归档完成后本地 WAL 仍需保留到槽不再需要。

一个常见误解是“我有复制槽,就等于有备份”。不是。槽没有数据文件,只有位置。

逻辑槽为什么还拽住旧行版本 ​

逻辑解码需要读取表的历史行版本,才能把变更还原成 INSERT/UPDATE/DELETE。因此逻辑槽除了 restart_lsn,还会维护 xmin(最老的、仍需为其保留行版本的事务)和 catalog_xmin(系统目录方面对应的最老事务)。

后果是:VACUUM 无法清理这些事务删除的元组。一个卡住的逻辑槽会让相关表不断膨胀,pg_stat_user_tables 里的 dead tuple 数只增不减,最终可能撑爆表空间。这比 WAL 膨胀更隐蔽,因为磁盘增长发生在数据目录而非 pg_wal。

处置逻辑槽卡死时,必须同时关注两处:WAL 保留和表膨胀。

槽名规范与命名冲突 ​

槽名只能包含小写字母、数字和下划线。这意味着它天然避免了大小写歧义,但也要注意不要与保留字或其它系统标识符混淆。

实践中建议给槽名加前缀以标明用途与消费者,例如物理槽用 p_ 或 phy_,逻辑槽用 l_ 或 cdc_,后面跟节点名。这样在 pg_replication_slots 里一眼就能看出哪个槽对应谁。

槽名是集群范围的唯一标识,与数据库无关。因此两个不同的数据库也不能用同一个槽名。

槽与连接槽(connection slot)的区别 ​

pg_stat_activity 里 backend_type = 'walsender' 的会话是 WAL 发送进程;max_wal_senders 限制同时运行的 walsender 数量。这属于“连接槽”层面的限制,和 max_replication_slots 不是一回事:

  • max_replication_slots 限制持久化的槽对象数量。
  • max_wal_senders 限制并发 walsender 进程数量。

两者都必须留有余量。尤其是 max_wal_senders,官方建议设得比预期客户端数量略高,因为客户端异常断开后连接槽要等超时才会释放。

pg_basebackup 与槽 ​

用 pg_basebackup 做流式备份时,如果没有指定槽,它可能因主库 WAL 回收而失败。可以用 --slot=slotname 指定一个槽(槽不存在时会自动创建),或让 pg_basebackup 自建临时槽。

需要注意的是:如果指定的是永久槽,且 pg_basebackup 进程异常终止而没有清理,槽会被留在主库上拽 WAL。因此更推荐让 pg_basebackup 使用临时槽,或在脚本中确保异常路径也清理槽。

槽的状态在重启后的恢复 ​

槽信息保存在 pg_replslot 目录下,是持久的。重启后槽依然存在,restart_lsn 也保持不变。但要注意:

  • 如果重启前槽已经 unreserved,重启不会自动把它恢复成 reserved。
  • pg_replication_slot_advance 的推进结果只在 checkpoint 时落盘,所以崩溃后可能回退。

因此判断槽状态必须基于重启后的实时查询,而不能依赖重启前的结论。

复制槽的复制(同步到备库) ​

较新版本支持把逻辑槽同步到备库,使得故障切换后逻辑复制能从新主库继续。相关字段包括 failover 与 synced。

在备库上,synced = true 的槽既不能用于逻辑解码,也不能手动删除。主库上这些字段没有实际意义。这部分属于故障切换范畴,本篇只做提示,不展开配置细节。

如何判断一个槽是否还有用 ​

判断槽能否删除,可以按以下顺序自查:

  1. active 是否为 true?如果长期为 true,说明有消费者,删了会断掉它。
  2. 消费者是谁?物理槽看从库配置里的 primary_slot_name;逻辑槽看订阅端的 slot_name。
  3. restart_lsn 是否长期不动?若一直不动且无消费者,基本是遗留槽。
  4. invalidation_reason 是否已置位?若是 wal_removed / lost,槽已经无意义。
  5. 业务上是否已下线对应节点?确认无影响后再删。

删除遗留槽是回收磁盘最快的手段之一,但删除前必须走完上述确认,避免误删活跃从库的槽。

槽与 pg_stat_replication 的分工 ​

pg_replication_slots 描述的是“槽这个对象本身”的状态,pg_stat_replication 描述的是“当前正在流复制的连接”的状态。两者关系如下:

  • 一个槽可能出现在 pg_stat_replication 中(有活跃 walsender),也可能只出现在 pg_replication_slots 中(无消费者)。
  • 反过来,pg_stat_replication 里可能有对应临时槽、或根本不用槽的连接。

因此“从库是否健康”要看 pg_stat_replication(含 state、sent_lsn、replay_lsn),而“WAL 会不会被拽住”要看 pg_replication_slots。做监控时不要只看其中一个。

流复制与 WAL 归档的 WAL 保留竞争 ​

同一份 WAL 同时被三方“惦记”:checkpoint 想回收它、槽想保留它、归档进程想把它推到归档目录。PostgreSQL 的规则是:

  • 归档未完成的 WAL 段不会被删除(需配置 archive_mode)。
  • 槽仍需要的 WAL 段不会被删除(受 max_slot_wal_keep_size 约束)。
  • 以上两者都不需要时,才由 checkpoint 回收。

所以磁盘占用是“取最坏情况”的叠加效果。一个归档卡住(例如归档目录写满)再叠加一个卡住的槽,磁盘消耗会被放大到两者之和。排查磁盘告警时,应当同时检查归档积压和槽滞后。

为什么 max_slot_wal_keep_size 的变更要谨慎 ​

官方对该参数的限定是:只能在 postgresql.conf 中或服务器命令行上设置。这意味着它不属于可以通过会话级 SET 临时调整的参数,变更需要走配置管理流程,并根据实际情况决定是否需要重启。

变更该参数属于高风险操作:调小可能立刻让槽进入 unreserved;调大则可能让磁盘更快被占满。建议在维护窗口内进行,并提前做好磁盘容量核算。

逻辑槽的创建时机与 wal_level ​

创建逻辑槽要求 wal_level 至少为 logical。而 wal_level 只能在服务启动时设置,这意味着:如果当前是 replica,必须先改配置并重启,才能创建逻辑槽。

另外,创建逻辑槽时主库若存在长时间运行的事务,槽的 xmin 会直接落到很旧的位置,导致大量旧行版本被拽住。因此在创建逻辑槽前先确认没有长事务,是一个容易被忽略的准备工作。

从库上的槽与主库上的槽 ​

从库(hot standby)也可能持有槽,主要出现在以下情形:

  • 级联复制:从库作为上游,为下游提供 WAL。
  • 逻辑槽同步(synced 为 true),用于故障切换后继续逻辑解码。

从库上的槽同样会拽住从库的 WAL。级联复制链条越长,上游的 WAL 保留压力越大,这一点在多层拓扑中需要单独核算容量。

删除槽之后的磁盘回收时机 ​

删除槽不会立即释放磁盘。pg_wal 里的 WAL 段要等到下一次 checkpoint 才会按新的保留需求回收。因此删除遗留槽后如果看到磁盘空间没变,属于正常现象,可以去观察 pg_wal 目录在下一个 checkpoint 之后的变化,而不是反复删除。

同理,回收速度受 checkpoint_timeout 与 max_wal_size 影响;在低写入量的实例上,可能等待较久。

槽的容量核算方法 ​

给槽规划磁盘,可以用一个粗略但实用的公式:

text
预留 WAL 空间 ≈ 最大可接受滞后时间 × 平均 WAL 生成速率 × 安全系数

例如平均每小时生成 2GB WAL,允许从库掉线 4 小时,安全系数取 2,则需要约 16GB 的额外预留。这个值应当与 max_slot_wal_keep_size 的取值互相印证:如果 max_slot_wal_keep_size 小于核算值,槽会在达到可接受滞后之前就被放弃。

槽与备份窗口的关系 ​

在“先做基础备份、再持续归档”的体系里,复制槽与备份窗口的交集出现在流式基础备份上。若使用 pg_basebackup 且关心它不失败,槽能保证所需 WAL 不被回收。

但要注意:pg_basebackup 需要的 WAL 是“备份期间产生的那部分”,备份结束后就不再需要。若使用了永久槽而没有回收,它就会一直在那里拽 WAL。这正是“备份留下的槽”成为常见遗留槽的原因。

判断槽是否为备份遗留 ​

遗留槽有一些典型特征,可以快速识别:

  • 名字类似 pg_basebackup_xxxx(流式备份自动创建的槽常带此类前缀)。
  • slot_type 为物理、active 长期为 false。
  • restart_lsn 停在很久之前的位置不再变化。
  • 没有从库配置引用该槽名。

命中上述特征时,仍应按“确认消费者 → 删除”的流程处理。

槽与时间点恢复的边界 ​

复制槽的进度信息虽然带 LSN,但它不能用来做 PITR。原因有三点:

  • 槽里没有数据页,只有一个 WAL 位置;PITR 必须有基础备份的数据文件。
  • 槽的 restart_lsn 只保证“WAL 没被删”,不保证“归档里有这份 WAL”。
  • 删除槽或槽失效后,pg_wal 里的 WAL 很快会被回收;只有归档是长期的。

因此正确的分工是:槽服务于“在线复制不断线”,归档服务于“历史时刻可回放”。两者共用同一批 WAL,但目标完全不同。

槽的命名与拓扑对应关系 ​

在有多套从库、多套逻辑订阅的拓扑里,建议用命名把拓扑映射出来,例如:

  • p_standby_shanghai:上海的物理从库。
  • p_standby_beijing:北京的物理从库。
  • l_cdc_warehouse:数仓的 CDC 逻辑槽。

这样一条巡检告警(例如 p_standby_beijing 处于 unreserved)能立刻定位到物理节点,而不需要再去查配置反查槽名。命名规范属于低成本、高回报的运维约定。

槽与 synchronous_commit 的交互 ​

同步复制会让事务提交等待从库确认,从而把 RPO 压到接近零。这里的“确认”依赖复制路径,而复制路径可以基于槽。需要区分两点:

  • 同步复制减少的是数据丢失窗口,不是磁盘风险。
  • 槽拽住 WAL 的风险与是否同步无关;异步复制同样会撑爆 pg_wal。

把“同步复制”误当成“备份”也是一个常见误解。同步复制只保证提交被确认,副本仍可能被误操作(例如 DROP TABLE)同步删除。

槽的批量审计思路 ​

当实例上槽数量较多时,逐个查看效率很低。可以按“是否活跃 + 状态是否正常”做一个二维分类,先处理最危险的一格:

sql
SELECT CASE WHEN active THEN 'active' ELSE 'idle' END AS usage,
       wal_status,
       count(*) AS slot_count
FROM pg_replication_slots
GROUP BY 1, 2
ORDER BY 1, 2;

idle + unreserved(空闲且 WAL 即将被回收)是最需要立刻处理的一类:要么唤醒消费者,要么确认后删除。active + unreserved 则表示消费者在跑但追不上,需要评估带宽或消费能力。

延迟复制场景下的槽 ​

延迟复制(recovery_min_apply_delay)会让从库刻意落后主库一段时间,用于“误操作后的兜底”。这种从库必定长期处于滞后状态,因此它的槽也会长期拽住大量 WAL——这是有意为之,不是故障。

部署延迟复制时,必须把“延迟时长 × WAL 生成速率”预先算进磁盘容量,并把该槽从“滞后即异常”的告警中排除,改为按容量阈值告警。

网络抖动与槽的假死 ​

网络抖动会让从库短时间断开,槽随之变为 active = false。这类“假死”通常几分钟内会自行恢复,不需要人工干预。需要区分的是:

  • 短暂断开:inactive_since 只前进几分钟,随后恢复为 active。
  • 长期掉线:inactive_since 持续前进,WAL 占用持续上升。

因此告警阈值不宜过小(否则频繁误报),但也不能过大(否则等发现时磁盘已满)。常见的做法是设置两级阈值:超过 10 分钟提醒、超过 1 小时升级为高优。

槽与 failover 槽的初步认识 ​

较新版本引入了“故障切换槽”的概念,允许把逻辑槽同步到备库,使主备切换后逻辑复制无需重建。相关列包括 failover 与 synced:

  • failover:该逻辑槽是否启用了向备库同步,物理槽恒为 false。
  • synced:该逻辑槽是否由主库同步而来;在备库上,synced = true 的槽既不能用于逻辑解码也不能手动删除。

需要特别注意 synced = true 的槽在 inactive_since 上的语义:它表示“最近一次同步停止的时间”,若一直处于同步状态则为 NULL。这与普通槽的含义不同,做监控时要避免套用同一套阈值。

槽的文件落盘位置 ​

永久槽的信息保存在数据目录下的 pg_replslot 子目录中,每个槽对应一个同名子目录。删除槽会移除对应文件,但同样要等 checkpoint 完成 WAL 回收。

排查磁盘占用时,pg_replslot 本身通常很小,真正占空间的是 pg_wal。因此不要误以为“删掉 pg_replslot 里的目录就能放磁盘”——那样做会破坏槽的元数据,属于不当操作。

槽的权限要求 ​

创建与删除槽需要相应的权限:pg_create_physical_replication_slot、pg_create_logical_replication_slot、pg_drop_replication_slot、pg_replication_slot_advance 这些函数默认仅限超级用户,或需要显式授予 EXECUTE 权限。

同时,建立复制连接本身需要具备 REPLICATION 属性的角色。因此在生产环境中,通常的做法是创建一个专用于复制的角色,并在从库或消费者侧使用它,而不是直接用超级用户。

监控查询示例 ​

下面这个查询把“谁在拽 WAL、拽了多少、还剩多少余量”放在一起,适合作为巡检脚本的基础:

sql
SELECT slot_name,
       slot_type,
       active,
       wal_status,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS lag,
       CASE WHEN safe_wal_size IS NULL THEN 'n/a'
            ELSE pg_size_pretty(safe_wal_size::numeric)
       END AS safe_remaining,
       invalidation_reason
FROM pg_replication_slots
ORDER BY lag DESC NULLS LAST;

配合刚才的字段解释,可以在一条查询里完成大部分判断。

实战示例 ​

示例一:查看所有槽的概况 ​

sql
SELECT slot_name, slot_type, active, wal_status, restart_lsn
FROM pg_replication_slots;
text
 slot_name  | slot_type | active | wal_status | restart_lsn
------------+-----------+--------+------------+-------------
 standby_a  | physical  | t      | reserved   | 0/3A1F2B90
 cdc_slot   | logical   | f      | extended   | 0/2C4E6A10
 legacy_old | physical  | f      | lost       |

legacy_old 的 wal_status 为 lost、restart_lsn 为 NULL,说明它已经失效且不再保留 WAL,可以确认无消费者后删除。

示例二:为从库创建物理槽 ​

在主库执行:

sql
SELECT * FROM pg_create_physical_replication_slot('standby_a');

在从库的配置中引用:

conf
primary_conninfo = 'host=192.168.1.10 port=5432 user=repl application_name=standby_a'
primary_slot_name = 'standby_a'

注意 primary_slot_name 只在从库侧设置;命名与主库上的槽名必须一致。

示例三:找出长期不活跃的槽 ​

sql
SELECT slot_name, slot_type, active, inactive_since, wal_status
FROM pg_replication_slots
WHERE active = false
ORDER BY inactive_since NULLS LAST;

inactive_since 由较新版本引入。若所用版本该列为 NULL,可退而观察 restart_lsn 是否长期不变。

示例四:查询槽拽住的 WAL 总量 ​

sql
SELECT slot_name,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained
FROM pg_replication_slots
WHERE restart_lsn IS NOT NULL
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) DESC;
text
 slot_name  | retained
------------+-----------
 cdc_slot   | 42 GB
 standby_a  | 512 MB

这个输出能立刻说明问题:cdc_slot 拽住了 42 GB 的 WAL,且若它 inactive,就是磁盘告警的直接原因。

示例五:清理已失效的遗留槽 ​

sql
-- 先确认无消费者
SELECT slot_name, active, invalidation_reason FROM pg_replication_slots
WHERE slot_name = 'legacy_old';
text
 slot_name  | active | invalidation_reason
------------+--------+--------------------
 legacy_old | f      | wal_removed
sql
SELECT pg_drop_replication_slot('legacy_old');

删除后 pg_wal 中不再被任何槽需要的段会在后续 checkpoint 被回收。

示例六:给槽设置 WAL 上限 ​

conf
max_slot_wal_keep_size = '20GB'

改完后需要重启(该参数只能在 postgresql.conf 或命令行设置)。重启后观察:

sql
SELECT slot_name, wal_status, pg_size_pretty(safe_wal_size::numeric) AS safe
FROM pg_replication_slots;

若某个槽的 safe 持续下降,说明它正在逼近被放弃的边界。

示例七:用推进函数跳过无用的 WAL ​

sql
SELECT * FROM pg_replication_slot_advance('cdc_slot', pg_current_wal_lsn());

该写法把槽直接推进到当前插入位置,等价于宣告“这段 WAL 我都不需要了”。仅在业务确认可以丢弃这段变更时使用,因为它会让消费者永久看不到这段数据。

示例八:区分“空闲”与“活跃但滞后” ​

sql
SELECT slot_name, active, wal_status,
       pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS lag_bytes
FROM pg_replication_slots
ORDER BY lag_bytes DESC NULLS LAST;

若 active = true 且 lag_bytes 很大且在增长,问题在“消费者追不上”;若 active = false 且 lag_bytes 很大,问题在“没有消费者”。两者的处置方向完全不同,前者调优/扩容,后者确认后清理。

示例九:记录槽的关键指标用于巡检 ​

sql
SELECT now() AS ts,
       slot_name,
       slot_type,
       active,
       wal_status,
       restart_lsn,
       safe_wal_size,
       invalidation_reason
FROM pg_replication_slots;

把这条查询接入定时采集(例如每 5 分钟一次)并落库,就能得到 safe_wal_size 的下降曲线,从而在触界之前预判。

注意事项 ​

  • 默认不限制 = 无限风险。max_slot_wal_keep_size 默认 -1,槽可以拽住任意多的 WAL。生产环境应显式评估后再决定取值。
  • pg_wal 写满会导致 PANIC 关闭,不是优雅拒绝。请把 pg_wal 放在独立且有余量的文件系统上。
  • 不要把槽当备份。槽只保存位置,不保存数据文件;没有归档和基础备份,槽无法恢复数据。
  • 逻辑槽还会拽住行版本,导致表膨胀。只看 pg_wal 大小是不够的。
  • max_replication_slots 改小会导致启动失败(当小于现有槽数量时)。缩容前先删槽。
  • 删除活跃槽会中断消费者。删除前确认 active 状态与消费者身份。
  • pg_replication_slot_advance 不会后退,且只在 checkpoint 时落盘,崩溃后可能回退。
  • safe_wal_size 为 NULL 不一定安全,可能只是没配 max_slot_wal_keep_size。
  • lost 不可恢复。槽变为 lost 后无法“修好”,必须重建消费者。
  • conflicting 由 PostgreSQL 16 引入,在更早版本上查询该列会报错,脚本需注意版本兼容。
  • 临时槽不会留下清理负担,需要一次性备份或短期消费时优先考虑。
  • 槽名区分大小写影响很小但有限制:只允许小写字母、数字、下划线。

最佳实践 ​

  1. 上线前显式决定 max_slot_wal_keep_size 的值,写进变更记录,并说明“保主库还是保从库”的策略取向。
  2. 对 pg_replication_slots 建立定时巡检,至少采集 slot_name、active、wal_status、restart_lsn、safe_wal_size。
  3. 对 wal_status = 'unreserved' 和 'lost' 分别设置不同等级的告警;unreserved 应当作为高优告警处理。
  4. 对 inactive_since(或 restart_lsn 长时间不变)设置空闲时长告警,避免遗留槽无人发现。
  5. 把 pg_wal 放在独立文件系统,容量比“正常 WAL 生成量 + 最大允许滞后量”再多留一档。
  6. 为槽命名引入用途前缀(如 p_ / cdc_),让巡检结果可以自解释。
  7. 删除槽遵循“确认 inactive → 确认消费者 → 确认业务无影响 → 删除”的四步流程,并留下操作记录。
  8. 使用逻辑槽时,同步监控相关表的 dead tuple 与表膨胀,而不只是 WAL 占用。
  9. 需要临时消费时优先使用临时槽,避免永久槽遗留。
  10. pg_basebackup 尽量使用槽但注意异常路径清理;或使用临时槽规避遗留。
  11. 任何调整 max_replication_slots 的操作都要先核对当前槽数量,避免启动失败。
  12. 把槽的监控接入与归档、pg_wal 容量同一个看板,因为这三个指标高度相关。

下一节

继续学习:pg_verifybackup

参考链接 ​