复制槽(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_slots | 10 | 服务端最多支持多少个槽 | 仅 server start |
max_slot_wal_keep_size | -1(不限制) | 槽最多能拽住多少 MB 的 WAL | 仅 postgresql.conf / 命令行 |
wal_keep_size | 0 | 为非槽消费者保底保留的 WAL 大小 | 仅 postgresql.conf / 命令行 |
wal_level | replica | 必须为 replica 或更高才能使用槽 | 仅 server start |
idle_replication_slot_timeout | 0(禁用) | 超过此时长未使用的槽会被失效处理 | 仅 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。
语法与用法
查看现有槽
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;创建物理槽
SELECT * FROM pg_create_physical_replication_slot('standby_a');在从库的 primary_conninfo 中指定 primary_slot_name = 'standby_a' 即可使用该槽。
创建逻辑槽
SELECT * FROM pg_create_logical_replication_slot('cdc_slot', 'pgoutput');第一个参数是槽名,第二个参数是输出插件名。插件必须是已安装且被信任的(见 output_plugin_libraries)。
删除槽
SELECT pg_drop_replication_slot('standby_a');如果槽仍处于活跃状态,删除会失败。此时应先停掉消费者,或用 pg_terminate_backend(active_pid) 终止对应会话(需谨慎)。
推进槽位置
SELECT * FROM pg_replication_slot_advance('standby_a', '0/1A2B3C48');该函数把槽的确认位置向前推进,且不会后退,也不会超过当前插入位置。返回槽名与推进到的实际位置。注意:更新后的位置信息在下次 checkpoint 时才落盘,因此崩溃后槽可能回退到较早的位置。
查看槽对应的 WAL 余量
SELECT slot_name,
wal_status,
pg_size_pretty(safe_wal_size::numeric) AS safe_remaining
FROM pg_replication_slots;计算槽滞后多少
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 的槽既不能用于逻辑解码,也不能手动删除。主库上这些字段没有实际意义。这部分属于故障切换范畴,本篇只做提示,不展开配置细节。
如何判断一个槽是否还有用
判断槽能否删除,可以按以下顺序自查:
active是否为 true?如果长期为 true,说明有消费者,删了会断掉它。- 消费者是谁?物理槽看从库配置里的
primary_slot_name;逻辑槽看订阅端的slot_name。 restart_lsn是否长期不动?若一直不动且无消费者,基本是遗留槽。invalidation_reason是否已置位?若是wal_removed/lost,槽已经无意义。- 业务上是否已下线对应节点?确认无影响后再删。
删除遗留槽是回收磁盘最快的手段之一,但删除前必须走完上述确认,避免误删活跃从库的槽。
槽与 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 影响;在低写入量的实例上,可能等待较久。
槽的容量核算方法
给槽规划磁盘,可以用一个粗略但实用的公式:
预留 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)同步删除。
槽的批量审计思路
当实例上槽数量较多时,逐个查看效率很低。可以按“是否活跃 + 状态是否正常”做一个二维分类,先处理最危险的一格:
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、拽了多少、还剩多少余量”放在一起,适合作为巡检脚本的基础:
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;配合刚才的字段解释,可以在一条查询里完成大部分判断。
实战示例
示例一:查看所有槽的概况
SELECT slot_name, slot_type, active, wal_status, restart_lsn
FROM pg_replication_slots; 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,可以确认无消费者后删除。
示例二:为从库创建物理槽
在主库执行:
SELECT * FROM pg_create_physical_replication_slot('standby_a');在从库的配置中引用:
primary_conninfo = 'host=192.168.1.10 port=5432 user=repl application_name=standby_a'
primary_slot_name = 'standby_a'注意 primary_slot_name 只在从库侧设置;命名与主库上的槽名必须一致。
示例三:找出长期不活跃的槽
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 总量
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; slot_name | retained
------------+-----------
cdc_slot | 42 GB
standby_a | 512 MB这个输出能立刻说明问题:cdc_slot 拽住了 42 GB 的 WAL,且若它 inactive,就是磁盘告警的直接原因。
示例五:清理已失效的遗留槽
-- 先确认无消费者
SELECT slot_name, active, invalidation_reason FROM pg_replication_slots
WHERE slot_name = 'legacy_old'; slot_name | active | invalidation_reason
------------+--------+--------------------
legacy_old | f | wal_removedSELECT pg_drop_replication_slot('legacy_old');删除后 pg_wal 中不再被任何槽需要的段会在后续 checkpoint 被回收。
示例六:给槽设置 WAL 上限
max_slot_wal_keep_size = '20GB'改完后需要重启(该参数只能在 postgresql.conf 或命令行设置)。重启后观察:
SELECT slot_name, wal_status, pg_size_pretty(safe_wal_size::numeric) AS safe
FROM pg_replication_slots;若某个槽的 safe 持续下降,说明它正在逼近被放弃的边界。
示例七:用推进函数跳过无用的 WAL
SELECT * FROM pg_replication_slot_advance('cdc_slot', pg_current_wal_lsn());该写法把槽直接推进到当前插入位置,等价于宣告“这段 WAL 我都不需要了”。仅在业务确认可以丢弃这段变更时使用,因为它会让消费者永久看不到这段数据。
示例八:区分“空闲”与“活跃但滞后”
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 很大,问题在“没有消费者”。两者的处置方向完全不同,前者调优/扩容,后者确认后清理。
示例九:记录槽的关键指标用于巡检
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 引入,在更早版本上查询该列会报错,脚本需注意版本兼容。- 临时槽不会留下清理负担,需要一次性备份或短期消费时优先考虑。
- 槽名区分大小写影响很小但有限制:只允许小写字母、数字、下划线。
最佳实践
- 上线前显式决定
max_slot_wal_keep_size的值,写进变更记录,并说明“保主库还是保从库”的策略取向。 - 对
pg_replication_slots建立定时巡检,至少采集slot_name、active、wal_status、restart_lsn、safe_wal_size。 - 对
wal_status = 'unreserved'和'lost'分别设置不同等级的告警;unreserved应当作为高优告警处理。 - 对
inactive_since(或restart_lsn长时间不变)设置空闲时长告警,避免遗留槽无人发现。 - 把
pg_wal放在独立文件系统,容量比“正常 WAL 生成量 + 最大允许滞后量”再多留一档。 - 为槽命名引入用途前缀(如
p_/cdc_),让巡检结果可以自解释。 - 删除槽遵循“确认 inactive → 确认消费者 → 确认业务无影响 → 删除”的四步流程,并留下操作记录。
- 使用逻辑槽时,同步监控相关表的 dead tuple 与表膨胀,而不只是 WAL 占用。
- 需要临时消费时优先使用临时槽,避免永久槽遗留。
pg_basebackup尽量使用槽但注意异常路径清理;或使用临时槽规避遗留。- 任何调整
max_replication_slots的操作都要先核对当前槽数量,避免启动失败。 - 把槽的监控接入与归档、
pg_wal容量同一个看板,因为这三个指标高度相关。
下一节
继续学习:pg_verifybackup
参考链接
- PostgreSQL 官方文档:
pg_replication_slots - PostgreSQL 官方文档:Replication Slots
- PostgreSQL 官方文档:Replication Management Functions
- PostgreSQL 官方文档:Replication Configuration
- PostgreSQL 官方文档:Log-Shipping Standby Servers
- PostgreSQL 官方文档:Streaming Replication Protocol
- PostgreSQL 官方文档:Logical Decoding
- PostgreSQL 官方文档:pg_basebackup
- PostgreSQL 官方文档:Write Ahead Log Configuration