暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

PostgreSQL复制槽失效排查实录:一场与WAL和事务ID的较量

原创 高达 2026-03-25
229

大家好~ 今天给大家带来一篇超实用的PostgreSQL技术干货投稿,来自我的朋友分享!(某头部制造车企首席DBA,PG ACE,10年通信/电力/证券/保险等行业一/二线数据库维护服务经验。 数据库技术爱好者,热衷于技术的学习研究和交流,网名“李先生”)

各位道友们,本篇文章全程贴合PG 实战场景问题处理,干货可直接收藏,快快点赞关注、扫码进群吧,再说一句,今天的配图是上次夜游西湖的大漂亮风景,希望大家会喜欢,话不多说,我们快快回顾一下今天的案例分享:

凌晨三点,监控告警突然响起——两个逻辑复制槽状态异常。这不是第一次了,但这次的情况似乎更加棘手。面对即将触发的连锁反应,一场与WAL日志和事务ID的较量悄然开始。

第一章:初探战场——发现失效的复制槽

凌晨的告警显示,生产环境的逻辑复制出现异常。作为DBA,我第一时间连接到主库,开始排查。

1.1 第一道命令:查看复制槽全景

SELECT slot_name, plugin, slot_type, active, active_pid, xmin, catalog_xmin,        restart_lsn, confirmed_flush_lsn, wal_status, safe_wal_size FROM pg_replication_slots;

执行结果:

                   slot_name                   |  plugin  | slot_type | active | active_pid | xmin | catalog_xmin | restart_lsn  | confirmed_flush_lsn | wal_status | safe_wal_size -----------------------------------------------+----------+-----------+--------+------------+------+--------------+--------------+---------------------+------------+--------------- postgres_lxs_0_0                         |          | physical  | t      |      76250 |      |              | 2C7/4F7C8290 |                     | reserved   |   21483453808 ods_oms_pmp_default_lxs_ri        | pgoutput | logical   | f      |            |      |    238237296 |              | 29F/353EE920        | lost       |               ods_oms_pmp_default_lxs_ri_prod   | pgoutput | logical   | f      |            |      |    240507166 |              | 2A3/F8192F30        | lost       |               ods_oms_pmp_default_lxs_ri_prod_1 | pgoutput | logical   | f      |            |      |    266139505 | 2C6/DC8D3E90 | 2C6/DCCF9528        | reserved   |   19554073968(4 rows)

🔍 初步分析:

复制槽名称
类型
状态
关键问题
postgres_lxs_0_0
物理
✅ 正常
ods_oms_pmp_default_lxs_ri
逻辑
❌ lost
WAL状态丢失
ods_oms_pmp_default_lxs_ri_prod
逻辑
❌ lost
WAL状态丢失
ods_oms_pmp_default_lxs_ri_prod_1
逻辑
⚠️ reserved
非活跃但WAL正常

发现关键问题: 两个逻辑复制槽的 wal_status 显示为 lost——这是致命的信号。WAL(Write-Ahead Log)丢失意味着复制槽无法继续工作。

第二章:深挖根源——WAL配置与延迟分析

2.1 检查WAL配置参数

SELECT name, setting, unit FROM pg_settings WHERE name IN ('wal_keep_size', 'max_slot_wal_keep_size', 'max_wal_size');

执行结果:

          name          | setting | unit ------------------------+---------+------ max_slot_wal_keep_size | 20480   | MB max_wal_size           | 8192    | MB wal_keep_size          | 1024    | MB(3 rows)

关键配置解读:

  • max_slot_wal_keep_size = 20480 MB
     (20GB):单个复制槽最多保留的WAL量
  • max_wal_size = 8192 MB
     (8GB):两次检查点间WAL最大估算值
  • wal_keep_size = 1024 MB
     (1GB):为复制保留的最小WAL量

2.2 计算复制槽延迟

SELECT'ods_oms_pmp_default_datacenter_secd_ri'AS slot_name,  pg_wal_lsn_diff(pg_current_wal_lsn(), '29F/353EE920') AS lag_bytes,  pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '29F/353EE920')::bigint) AS lag_pretty;

执行结果:

               slot_name                |  lag_bytes   | lag_pretty ----------------------------------------+--------------+------------ ods_oms_pmp_default_datacenter_secd_ri | 172244281184 | 160 GB(1 row)

💡 瞬间明白:

项目
数值
状态
复制槽延迟
160 GB
🚨 严重超标
允许保留上限
20 GB
⚠️ 配置限制
超出比例
800%
❌ 必然失效

根本原因浮出水面: 复制槽延迟达到160GB,远超20GB的限制。PostgreSQL不得不丢弃旧WAL,导致复制槽状态变为lost。

2.3 检查订阅关联情况

SELECT subname, subslotname, subenabled FROM pg_subscription WHERE subslotname IN ('ods_oms_pmp_default_datacenter_secd_ri', 'ods_oms_pmp_default_datacenter_secd_ri_prod');

执行结果:

 subname | subslotname | subenabled ---------+-------------+------------(0 rows)

重要发现: 两个失效的复制槽没有关联的订阅,它们是孤儿状态。

第三章:意外发现——第三个复制槽的隐患

在清理两个失效复制槽之前,我注意到第三个复制槽的异常。

3.1 深入分析第三个复制槽

SELECT    slot_name,    slot_type,    active,    active_pid,    catalog_xmin,    pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn) as lag_bytes,    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)::bigint) as lag_pretty,    wal_status,    safe_wal_sizeFROM pg_replication_slots WHERE slot_name ='ods_oms_pmp_default_datacenter_secd_ri_prod_1';

执行结果:

                   slot_name                   | slot_type | active | active_pid | catalog_xmin | lag_bytes  | lag_pretty | wal_status | safe_wal_size -----------------------------------------------+-----------+--------+------------+--------------+------------+------------+------------+--------------- ods_oms_pmp_default_datacenter_secd_ri_prod_1 | logical   | f      |            |    266139505 | 2005052416 | 1912 MB    | reserved   |   19472957144(1 row)

问题矩阵:

指标
状态
风险等级
活跃状态
❌ 非活跃
订阅关联
❌ 无订阅
延迟
⚠️ 1.9GB且在增长
WAL状态
✅ reserved
catalog_xmin
⚠️ 266139505
极高

3.2 检查事务ID年龄——惊人发现

SELECT    datname,    age(datfrozenxid) as frozen_xid_age,CASEWHEN age(datfrozenxid) >200000000THEN'CRITICAL: 接近事务ID回卷'WHEN age(datfrozenxid) >100000000THEN'WARNING: 事务ID年龄过高'ELSE'OK'ENDas statusFROM pg_database WHERE datname = current_database();

执行结果:

 datname  | frozen_xid_age |          status          ----------+----------------+-------------------------- postgres |      266402368 | CRITICAL: 接近事务ID回卷(1 row)

🚨 红色警报:

PostgreSQL使用32位事务ID,最大值约20亿。当前数据库的冻结事务ID年龄已达到2.66亿,状态显示"接近事务ID回卷"。如果达到20亿,数据库将进入只读模式防止数据损坏!

第四章:关键连接——catalog_xmin的阻塞效应

4.1 确认catalog_xmin的影响

SELECT'catalog_xmin'as source,266139505::text as xmin_value,    txid_current() -266139505as age_from_currentUNIONALLSELECT'current_xid'as source,    txid_current()::text as xmin_value,0as age_from_current;

执行结果:

    source    | xmin_value | age_from_current --------------+------------+------------------ catalog_xmin | 266139505  |           268551 current_xid  | 266408056  |                0(2 rows)

连接点发现:

  • 复制槽的 catalog_xmin = 266,139,505
  • 当前事务ID:266,408,056
  • 两者差距:268,551个事务

这意味着VACUUM无法清理这268,551个事务中产生的死元组。复制槽的catalog_xmin像一道闸门,挡住了事务清理的通道。

4.2 检查活跃事务持有者

SELECT    pid,    usename,    application_name,    backend_type,    backend_xmin::text,    backend_xid::text,    query_start,    state,LEFT(query, 100) as short_queryFROM pg_stat_activity WHERE backend_xmin ISNOT NULLORDERBY backend_xmin::text::bigintLIMIT 5;

执行结果:

   pid   | usename  | application_name |  backend_type  | backend_xmin | backend_xid |          query_start          | state  |       short_query       ---------+----------+------------------+----------------+--------------+-------------+-------------------------------+--------+------------------------- 1555972 | postgres | psql             | client backend | 266408056    |             | 2026-01-22 14:00:22.896688+08 | active | SELECT                 +         |          |                  |                |              |             |                               |        |     pid,               +         |          |                  |                |              |             |                               |        |     usename,           +         |          |                  |                |              |             |                               |        |     application_name,  +         |          |                  |                |              |             |                               |        |     backend_type,      +         |          |                  |                |              |             |                               |        |     backend_xmin::text,+         |          |                  |                |              |             |                               |        |     ba(1 row)

确认: 复制槽的catalog_xmin确实是当前系统中最老的事务点!

第五章:决策与行动——清理孤儿复制槽

5.1 最终状态确认

SELECT    slot_name,    active,    wal_status,    catalog_xmin::text,    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) as lag_prettyFROM pg_replication_slots WHERE slot_name ='ods_oms_pmp_default_datacenter_secd_ri_prod_1';

执行结果:

                   slot_name                   | active | wal_status | catalog_xmin | lag_pretty -----------------------------------------------+--------+------------+--------------+------------ ods_oms_pmp_default_datacenter_secd_ri_prod_1 | f      | reserved   | 266139505    | 2134 MB(1 row)

数据变化趋势:

  • 延迟从1912MB → 2134MB(15分钟内增长222MB)
  • 确认无人消费,持续积累

5.2 执行清理操作

基于以上分析,决定清理所有孤儿复制槽:

第一步:删除两个失效的复制槽

-- 删除第一个失效槽SELECT pg_drop_replication_slot('ods_oms_pmp_default_datacenter_secd_ri');-- 删除第二个失效槽  SELECT pg_drop_replication_slot('ods_oms_pmp_default_datacenter_secd_ri_prod');

第二步:删除第三个有隐患的复制槽

SELECT pg_drop_replication_slot('ods_oms_pmp_default_datacenter_secd_ri_prod_1');

第三步:验证清理结果

SELECT slot_name, active, wal_status FROM pg_replication_slots;

预期结果:

       slot_name       | active | wal_status -----------------------+--------+------------ postgres_109fe3a1_0_0 | t      | reserved(1 row)

第六章:经验总结与预防措施

6.1 问题根源分析

问题
原因
影响
复制槽失效
延迟超过max_slot_wal_keep_size
逻辑复制中断
事务ID年龄过高
catalog_xmin阻塞VACUUM
接近事务ID回卷风险
孤儿复制槽积累
消费者异常退出未清理
资源浪费和潜在风险

6.2 监控指标建议

建立以下监控告警:

  1. 复制槽延迟 > max_slot_wal_keep_size的80%
  2. wal_status = lost 立即告警
  3. 事务ID年龄 > 1亿 告警
  4. 复制槽活跃状态 > 1小时非活跃告警

6.3 预防措施

-- 定期检查孤儿复制槽SELECT slot_name, active, pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn) as lagFROM pg_replication_slots WHERE active =falseAND slot_type ='logical'AND pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn) >1024*1024*1024; -- 延迟>1GB-- 监控事务ID年龄SELECT datname, age(datfrozenxid) as frozen_xid_ageFROM pg_database WHERE age(datfrozenxid) >100000000;

后记:凌晨四点的思考

处理完这个紧急问题,窗外已现微光。这次事件提醒我们几个重要原则:

  1. 复制槽不是"设置即忘记":它们需要持续监控和维护
  2. 延迟是沉默的杀手:缓慢积累的延迟最终会导致灾难性故障
  3. 事务ID回卷是真实威胁:特别是在高负载的OLTP系统中
  4. 自动化清理很重要:建立自动清理孤儿复制槽的机制

最深刻的教训是:在分布式系统中,一个组件的故障往往会引发连锁反应。复制槽失效不仅仅是复制问题,它可能演变成整个数据库的可用性问题。

关键数字回顾:

  • 🔴 160 GB:失效复制槽的延迟
  • 🔴 2.66亿:数据库事务ID年龄
  • 🔴 268,551:被阻塞的事务数量
  • 🟢 20 GB:复制槽WAL保留上限
  • 🟢 1.9 → 2.1 GB:第三个复制槽延迟增长

这次排查像一次侦探工作,从表面的复制故障,追踪到WAL配置,最终发现隐藏在深处的事务ID危机。每个数字背后,都是系统状态的密码。

如果你在运维PostgreSQL时,也遇到类似问题,欢迎在评论区留言交流,一起探讨解决方案,少踩坑、提效率

同时也欢迎大家扫码入技术分享群,和我们一起学习进步,快快扫描如上二维码吧!

最后修改时间:2026-04-02 11:40:02
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论