GBase 8c 逻辑复制槽停住以后,xlog 为什么越堆越多
我最近看 GBase 8c 逻辑复制槽相关资料时,比较有感触的一点是:复制槽不是同步工具随手建出来的临时对象,它更像数据库端给下游消费方保留变更流的一份承诺。下游没有确认消费,数据库就要继续保留对应位置之后的 xlog;同步任务已经废弃但槽还留着,最后经常表现成"业务没什么大操作,磁盘却一直涨"。
这类问题在异构同步、准实时入仓、割接验证、双写过渡阶段都容易遇到。应用侧看到的是同步延迟,运维侧看到的是磁盘告警,数据库侧真正要看的往往是逻辑复制槽有没有推进。我自己理解下来,GBase 8c 的逻辑复制槽要当成日常巡检对象,而不是等 xlog 目录快满了才临时处理。
先看槽,不要先删文件
逻辑复制通过反解 xlog 生成逻辑变更流,下游按变更做同步。复制槽保存了消费位置,常见插件可以使用 wal2json。排查时我最关注下面几个字段:
| 字段 | 现场含义 | 我会怎么判断 |
|---|---|---|
slot_name |
槽名称 | 是否能对应到同步链路台账 |
active |
是否有客户端连接 | 长期为 false 要确认是否遗留 |
restart_lsn |
槽需要的最早 xlog 位置 | 和当前 xlog 位置差距越大,保留压力越大 |
confirmed_flush |
下游确认消费的位置 | 多次采样不变,说明消费没有推进 |
plugin |
输出插件 | 是否符合链路设计,例如 wal2json |
catalog_xmin |
逻辑槽保留系统表变更边界 | 长期不动时要留意清理和版本影响 |
我不建议看到 xlog 目录大就先到系统层面删文件。对逻辑复制槽来说,数据库保留 xlog 是为了保证下游还能从断点继续消费,绕过数据库直接处理文件,很容易把问题从"空间风险"变成"同步断点丢失"。
参数设计不要只按当前一条链路算
逻辑复制槽使用前,至少要把几个参数和权限想清楚。真正落到现场时,很多问题不是函数不会用,而是槽数量、主备切换、安全访问这些边界没规划。
| 配置项 | 常见方向 | 落地提醒 |
|---|---|---|
wal_level |
logical |
允许逻辑解析 xlog |
enable_slot_log |
on |
需要主备切换后保留槽信息时要重点验证 |
max_replication_slots |
按物理槽、备份槽、逻辑槽合计预留 | 不要只给正式链路留名额,测试和割接槽也会占用 |
| replication 访问规则 | 只放通同步服务网段 | 避免为了方便写过宽网段 |
| 同步账号 | 授予 REPLICATION 等必要权限 |
不建议直接拿业务高权限账号跑 CDC |
示例配置可以这样检查:
sql
show wal_level;
show enable_slot_log;
show max_replication_slots;
select slot_type, count(*)
from pg_replication_slots
group by slot_type;
示例环境里,我会把同步网段和账号单独管起来,下面地址只是占位写法:
bash
gs_guc reload -Z coordinator -N all -I all -c "wal_level = logical"
gs_guc reload -Z coordinator -N all -I all -c "enable_slot_log = on"
gs_guc reload -Z coordinator -N all -I all \
-h "host replication cdc_reader 10.18.40.0/24 sha256"
wal_level 这类参数是否需要重启,要结合实际版本和生效状态确认。我更倾向于在变更窗口处理,不在业务高峰临时修改。
创建槽以后马上纳入巡检
只要新增逻辑复制槽,我会同步补一条巡检 SQL。先看槽列表:
sql
select
slot_name,
database,
plugin,
slot_type,
active,
restart_lsn,
confirmed_flush,
xmin,
catalog_xmin
from pg_replication_slots
where slot_type = 'logical'
order by slot_name;
再估算当前 xlog 位置和 restart_lsn 之间的差距。这个数不完全等同于实际目录占用,但很适合看趋势:
sql
select
slot_name,
database as datname,
plugin,
active,
restart_lsn,
confirmed_flush,
pg_size_pretty(
pg_xlog_location_diff(
case
when pg_is_in_recovery() then pg_last_xlog_receive_location()
else pg_current_xlog_location()
end,
restart_lsn
)
) as retained_xlog_estimate
from pg_replication_slots
where slot_type = 'logical'
order by active, slot_name;
我一般按下面几类状态拆开看:
| 状态 | 可能原因 | 处理倾向 |
|---|---|---|
active=true 且保留量稳定 |
下游正常消费 | 持续观察 |
active=false 且保留量很小 |
可能是计划暂停或低频任务 | 先确认任务计划 |
active=false 且保留量持续变大 |
槽停滞或遗留 | 联系同步侧确认是否恢复、推进或删除 |
active=true 但保留量持续变大 |
连接还在,但消费跟不上 | 查目标端写入、网络、错误队列和同步吞吐 |
这里我不会用"非活跃就告警"这么粗的规则。更实用的是组合判断:非活跃持续时间、xlog 保留增长速度、磁盘剩余空间、同步任务状态。
peek 和 get 的差别很容易坑到人
逻辑复制槽有两个动作很像:peek 是看变更但不推进,get 是取变更并推进。调试时用错,短期看不出问题,时间长了 xlog 就会被槽拖住。
| 动作 | 函数 | 是否推进槽 | 适合场景 |
|---|---|---|---|
| 预览变更 | pg_logical_slot_peek_changes |
否 | 调试输出格式、临时排查 |
| 消费变更 | pg_logical_slot_get_changes |
是 | 正式 CDC 消费 |
| 手动推进 | pg_replication_slot_advance |
是 | 明确跳过某段日志 |
示例:只查看数据,不推进槽。
sql
select data
from pg_logical_slot_peek_changes(
'slot_cdc_order_202605',
null,
null,
'pretty-print',
'1'
);
示例:消费数据并推进槽。
sql
select data
from pg_logical_slot_get_changes(
'slot_cdc_order_202605',
null,
1000,
'pretty-print',
'1'
);
遇到"同步程序能看到变更,但数据库日志目录还在涨"的情况,我会优先核对程序到底调用的是 peek_changes 还是 get_changes。自研脚本里这个问题并不少见。
停滞以后按顺序排,不要一上来 drop
复制槽停住后,drop slot 确实能释放保留压力,但也可能让下游断点直接丢失。我自己的处理顺序一般是:先确认归属,再确认是否还能消费,最后再决定推进还是删除。
sql
select
slot_name,
database,
plugin,
active,
restart_lsn,
confirmed_flush
from pg_replication_slots
where slot_type = 'logical'
order by slot_name;
如果槽名能对应到正在运行的同步链路,先查同步任务。常见根因包括目标库写入慢、目标端约束冲突、网络抖动、错误队列堆积、同步程序只连着但没有真正消费。
如果确认是历史测试槽或废弃链路,再删除:
sql
select pg_drop_replication_slot('slot_tmp_check_202604');
如果下游已经通过全量重刷、断点重置等方式确认旧变更不再需要,可以考虑推进槽。这个动作我会要求留下操作记录,因为它解决的是数据库端保留压力,不自动保证目标端数据完整。
sql
select pg_replication_slot_advance('slot_cdc_order_202605', '0/7A3D920');
主备切换要把槽一起演练
enable_slot_log 的作用是控制逻辑复制槽主备同步特性。我的理解是,如果现场有高可用切换,又希望逻辑复制链路在切换后尽量连续,就不能只验证数据库主备能切过去,还要验证槽信息和消费断点。
| 场景 | enable_slot_log=off |
enable_slot_log=on |
|---|---|---|
| 主库创建逻辑槽 | 备库可能看不到槽 | 备库可以看到槽信息 |
| 主备切换后 | 新主可能缺少原槽,链路需要重建 | 新主保留槽信息,更利于恢复 |
| 运维验证点 | 切换后是否需要重新建槽 | 切换后消费端能否重新连接并推进 |
真正做 HA 演练时,我会在切换前后各查一次 pg_replication_slots,再让同步链路产生一小批可校验变更。只看数据库角色切换成功,不看逻辑复制槽,后面排同步问题会比较被动。
无主键表要提前识别
逻辑复制不只是 insert 能同步就行,update/delete 更容易踩坑。无主键表如果没有合适的记录级别,删除事件可能无法被正确捕获。我的做法是先扫参与同步的表,找出没有主键的对象:
sql
select
n.nspname as schema_name,
c.relname as table_name,
c.relreplident
from pg_catalog.pg_class c
join pg_catalog.pg_namespace n on n.oid = c.relnamespace
where c.relkind = 'r'
and n.nspname not in ('pg_catalog', 'information_schema')
and not exists (
select 1
from pg_catalog.pg_constraint con
where con.conrelid = c.oid
and con.contype = 'p'
)
order by n.nspname, c.relname;
核心同步表,我更倾向于补主键。短期无法补主键的表,可以评估使用:
sql
alter table ods.trade_order_ext replica identity full;
但这不是无成本动作。REPLICA IDENTITY FULL 会让旧行信息记录得更完整,对行宽大、更新删除频繁的表,xlog 量会明显增加。所以它适合兜底,不适合当成所有无主键表的默认处理。
处理方案怎么取舍
| 动作 | 适用情况 | 风险点 |
|---|---|---|
| 重启同步任务 | 槽还在,断点有效 | 目标端错误没修好会继续失败 |
| 修复目标端写入 | 连接存在但消费变慢 | 排查期间 xlog 继续增长 |
pg_replication_slot_advance |
下游确认可跳过旧日志 | 可能造成目标端缺变更 |
pg_drop_replication_slot |
槽确认废弃 | 原断点丢失 |
| 临时扩容磁盘 | 保留量增长快且排查需要时间 | 只能止血,不能替代根因处理 |
从落地角度看,GBase 8c 逻辑复制槽要和同步链路台账、HA 演练、xlog 空间监控一起管。创建槽、消费槽、推进槽、删除槽,每一步最好都能对应到明确的业务目的。这样遇到 xlog 堆积时,排查路径会清楚很多:先看槽,再看消费,再看主备,再看无主键表,而不是只盯着磁盘目录做清理。
参考资料
text
南大通用GBase 8c逻辑复制槽实践解析
https://www.gbase.cn/community/post/7148
南大通用GBase 8c逻辑复制槽功能实践示例
https://www.gbase.cn/community/post/5625
GBase8c使用wal2json逻辑复制槽
https://www.gbase.cn/community/post/4065
"G"术时刻 | GBase数据库逻辑复制槽功能应用实践
https://www.gbase.cn/news/3730
配置 Gbase 8c 数据节点 | DataPipeline 用户操作指南
https://docs.datapipeline.com/v4.4/rf6qi9891doe44zv/dzdm28q2vhtxgmic/fqszhng5l9m8gqyg/wr47yqspdsd6l1m7/gv5o6elrg1v2lpv7/