wal2json 的使用和注意事项

wal2json 的使用和注意事项

wal2json 是 PostgreSQL 的一个 逻辑解码插件,可以把 WAL 日志解析成 JSON 格式的 DML 变更记录,从而间接得到“可读的 SQL”。下面我分成 使用步骤 和 注意事项 两部分整理给你。

一、wal2json 的使用步骤

1. 启用逻辑解码

在 postgresql.conf 修改参数(需要重启数据库):

wal_level = logical
max_wal_senders = 4
max_replication_slots = 4
max_replication_connections = 4

如果数据库有大量写操作,最好调大 max_slot_wal_keep_size(防止 replication slot 没消费导致 WAL 爆仓)。

2. 确认插件已安装

在 PostgreSQL 的 contrib 目录通常有 wal2json。

SELECT * FROM pg_available_extensions WHERE name = 'wal2json';

如果没有,需要编译安装:

git clone https://github.com/eulerto/wal2json.git
cd wal2json
make && make install

3. 创建逻辑复制槽

SELECT * FROM pg_create_logical_replication_slot('myslot', 'wal2json');

4. 生成变更数据

随便执行一些 DML:

INSERT INTO emp(id, name) VALUES (1, 'Tom');
UPDATE emp SET name='Jerry' WHERE id=1;
DELETE FROM emp WHERE id=1;

5. 获取 WAL 变更并转 JSON

消费(返回 + 删除pg_logical_slot_get_changes)
SELECT * FROM pg_logical_slot_get_changes('myslot', NULL, NULL);

输出类似:
{"change":[{"kind":"insert","schema":"public","table":"emp","columnnames":["id","name"],"columnvalues":[1,"Tom"]}]}
{"change":[{"kind":"update","schema":"public","table":"emp","oldkeys":{"keynames":["id"],"keyvalues":[1]},"columnnames":["name"],"columnvalues":["Jerry"]}]}
{"change":[{"kind":"delete","schema":"public","table":"emp","oldkeys":{"keynames":["id"],"keyvalues":[1]}}]}

对应 SQL:

INSERT INTO public.emp (id, name) VALUES (1, 'Tom');
UPDATE public.emp SET name='Jerry' WHERE id=1;
DELETE FROM public.emp WHERE id=1;

pg_logical_slot_get_changes和pg_logical_slot_peek_changes

pg_logical_slot_get_changes() 的行为是一次性消费
只要它成功返回一批 WAL 记录,PostgreSQL 会立即把该槽的 confirmed_flush_lsn 推进到这批记录的末尾,槽里已经返回的 WAL 就被认为“已处理”。因此

  • 同一个 slot 再次调用时,已经没有更早的未消费记录;
  • 除非期间又有新事务产生,否则结果集就是空的——这正是你看到“查一次就再也查不到”的原因

验证:
1.查 slot 当前位置

SELECT slot_name, confirmed_flush_lsn
FROM pg_replication_slots
WHERE slot_name = 'myslot_dbtest';

再产生一点新变更

INSERT INTO test VALUES (1);
SELECT * FROM pg_logical_slot_get_changes('myslot_dbtest', NULL, NULL);

就能看到仅包含刚才那条新插入的 JSON,而旧数据不会再出现。

pg_logical_slot_peek_changes

想要“反复查看”而不清空,怎么办?
用 只读接口 即可:

SELECT * FROM pg_logical_slot_peek_changes('myslot_dbtest', NULL, NULL);

peek_changes 不会推进槽位,可以多次执行,同一份 WAL 会反复返回,适合做调试或多次采样

总结

  • get_changes = 消费(返回 + 删除)。
  • peek_changes = 偷看(返回 + 保留)。
    所以“查询一次之后数据就查询不到”是 正常且预期 的行为,并非配置错误或插件缺失

二、注意事项 ⚠️

1.插件要求

  • PostgreSQL 9.4+ 支持逻辑解码;
  • 建议 PostgreSQL 10+ 使用(对 wal2json 兼容性更好)。

2.事务粒度

  • wal2json 会按事务输出,包含 begin、commit,在跨事务解析时要注意拼接。

3.性能影响

  • 开启 wal_level=logical 会增加 WAL 大小;
  • 逻辑复制槽如果长时间不消费,会导致 WAL 文件无法清理,可能撑爆磁盘。
  • 一定要设置 max_slot_wal_keep_size 或及时调用 pg_drop_replication_slot。

4.主键要求

  • UPDATE 和 DELETE 输出 oldkeys 时,需要表有 主键或 REPLICA IDENTITY。
  • 没有主键时,建议:
ALTER TABLE emp REPLICA IDENTITY FULL;

否则无法生成删除/更新条件。

5.过滤选项

wal2json 有参数可过滤输出,比如:

SELECT * FROM pg_logical_slot_get_changes(
  'myslot', NULL, NULL,
  'include-types', '1',
  'include-transaction', '1',
  'pretty-print', '1'
);

include-types=1 输出列类型;
include-transaction=1 输出事务 id;
pretty-print=1 格式化 JSON。

6.输出转换

wal2json 输出 JSON,不是 SQL;需要业务侧写解析器,把 JSON 转换为 INSERT/UPDATE/DELETE 语句或直接同步到目标存储。

✅ 总结:

wal2json 的正确姿势是:

  • 开启逻辑解码 → 创建 replication slot → 获取 JSON → 解析为 SQL。
  • 注意清理 replication slot、表主键配置和 WAL 爆仓风险。

常见问题

复制槽一直不消费会带来的影响

如果创建了逻辑复制槽(slot)却一直不消费,PostgreSQL 会:

  • 1.保留从该槽的 confirmed_flush_lsn 开始以后所有的 WAL 文件(哪怕这些事务早已提交、表早已更新)。
  • 2.导致 pg_wal(或 pg_xlog)目录持续膨胀,把磁盘打满只是时间问题。
  • 3.任何试图删除仍在槽需要的 WAL 的操作(如 pg_archivecleanup、wal-g delete)都会被 PostgreSQL 拒绝,并出现类似如下的日志。
ERROR:  cannot remove file "000000010000000000000012" — still needed by replication slot "myslot_dbtest"

如何快速验证槽正在“拖住”多少 WAL

-- 1. 看槽当前位置
SELECT slot_name, restart_lsn, confirmed_flush_lsn,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS wal_held
FROM pg_replication_slots
WHERE slot_name = 'myslot_dbtest';

-- 2. 看磁盘上实际堆积了多少
SELECT file_name,
       pg_size_pretty(sum(size)) AS total_size
FROM pg_ls_waldir()
WHERE file_name >= (SELECT restart_lsn FROM pg_replication_slots
                    WHERE slot_name = 'myslot_dbtest')
GROUP BY file_name
ORDER BY file_name;

如果 wal_held 从几百 MB 涨到几十 GB,就证明 WAL 正在堆积。

多久能把磁盘撑爆?
取决于业务写入量。举例:

  • 业务平均 10 MB/s 写入
  • 槽 3 天无人消费
    → 大约 2.5 TB 的 WAL 会留在 pg_wal,很容易把 2 TB 盘直接写满,实例随后进入 PANIC 或 read-only 状态。

监控/告警建议

  • 1.对 pg_replication_slots.restart_lsn 与 pg_current_wal_lsn() 的差值做告警(阈值 1 GB/10 GB 视业务而定)。
  • 2.对 pg_stat_archiver/pg_ls_waldir() 的文件数、总大小做磁盘级告警。
  • 3.对 slot.active = f 且 wal_held > x 的槽直接发页——说明有人建完 slot 就忘了消费。

万一已经堆积,如何自救
1.确认真的不再需要该槽:

SELECT pg_drop_replication_slot('myslot_dbtest');

槽一删,PostgreSQL 会立即把不再需要的 WAL 标记为可删除,下次 checkpoint/autovacuum 会回收,磁盘空间回落。

2.如果还想保留槽,但想先“止血”:
- 临时把 max_slot_wal_keep_size(PG 13+)设为一个非零值,例如 10GB,到达上限后 PostgreSQL 会强制断开该槽并删除多余 WAL,代价是复制链断裂,需要重建槽。
3.磁盘已满导致无法启动:

  • 手动挪走/挂载更大盘 → 启动 → 先删槽或立即消费 → 再收缩。

一句话总结

  • 逻辑复制槽不消费 ≈ 磁盘定时炸弹;
  • 要么持续消费,要么及时删除,务必加监控。
posted @ 2026-05-18 10:35  数据库小白(专注)  阅读(47)  评论(0)    收藏  举报