绕过逻辑解码:WalShadow 如何从 PostgreSQL 物理 WAL 直达 ClickHouse

2026-09-23 28 预计阅读时间: 1 分钟
来源: postgr.es AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

WalShadow 选择了一条少见而大胆的复制路径:不使用 PostgreSQL 逻辑解码,而是直接读取流复制副本接收的物理 WAL 字节,自行解析 heap 记录,再写成 ClickHouse 原生数据块。

这不只是换了一个数据接口。它意味着复制工具需要理解 PostgreSQL 存储层的语言,同时还要把面向事务恢复的日志,转换成适合列式分析的批量数据。潜在收益很直接,但工程边界也比普通 CDC 更锋利。

物理 WAL 与逻辑变更流不是同一种抽象

典型的 PostgreSQL CDC 链路依赖逻辑解码:数据库先把 WAL 解释为较稳定的行级事件,例如某张表插入了一行、更新了哪些列,或者删除了哪个键。下游消费的是接近业务语义的变更流。

WalShadow 则站在更低的一层。物理 WAL 的首要目的,是让 PostgreSQL 能够恢复页面并维护物理副本,而不是给外部系统提供结构稳定的事件 API。因此,读取这些字节的程序不能只认识 INSERTUPDATEDELETE,还需要面对存储层细节,例如:

  • relation、文件节点与表对象之间的映射;
  • heap tuple 的物理布局和可见性信息;
  • HOT update、TOAST 值和 full-page image;
  • 事务提交、回滚以及跨记录重组;
  • checkpoint、崩溃续传和 WAL 回收;
  • DDL 或 PostgreSQL 版本变化带来的格式兼容问题。

这也是这条路线“勇敢”的地方:逻辑解码替调用方吸收了许多内部复杂度,而直接解析物理 WAL,相当于主动接管这部分责任。

为什么还要走这条路

低层接口并非只有成本。绕过逻辑解码后,复制程序可以更直接地消费与物理副本相同的字节流,并按照 ClickHouse 的写入方式组织数据。

ClickHouse 更喜欢成批写入的列式数据,而不是逐行执行事务型更新。WalShadow 将解析结果写成 ClickHouse-native blocks,意味着它可以在转换阶段积累一批记录,按列组织并一次提交,从而减少大量小请求和逐行序列化的开销。

不过,“能解析 WAL”不等于“已经获得正确的分析表”。实际系统仍需明确以下语义:

  1. 更新如何表示:在 ClickHouse 中追加新版本,还是转换成某种可合并结构?
  2. 删除如何传播:记录 tombstone、维护符号列,还是执行异步 mutation?
  3. 事务边界是否保留:同一事务中的多表变更何时对分析端可见?
  4. 重复投递如何处理:进程在写入成功但尚未记录进度时崩溃,会不会重复写入?
  5. DDL 如何同步:源表新增列、修改类型或重建表后,下游映射是否仍然有效?

这些决策通常比“每秒能解析多少 WAL”更影响生产可用性。

动手观察 WalShadow 所面对的字节流

下面的实验不安装或模拟 WalShadow,也不代表它的真实配置格式;它只使用 PostgreSQL 自带的 pg_receivewal,展示物理 WAL 消费者看到的入口。假设本机已经安装 Docker,可直接复制运行:

# 启动一个临时 PostgreSQL 16 实例
docker rm -f pg-wal-lab 2>/dev/null || true
docker run -d \
  --name pg-wal-lab \
  -e POSTGRES_PASSWORD=postgres \
  -p 55432:5432 \
  postgres:16

# 等待数据库就绪
until docker exec pg-wal-lab pg_isready -U postgres >/dev/null 2>&1; do
  sleep 1
done

# 创建测试表和一个物理复制槽
docker exec -i pg-wal-lab psql -U postgres <<'SQL'
CREATE TABLE events (
    id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    event_type text        NOT NULL,
    payload    jsonb       NOT NULL,
    created_at timestamptz NOT NULL DEFAULT now()
);

SELECT * FROM pg_create_physical_replication_slot('walshadow_lab');
SQL

# 在容器内启动物理 WAL 接收器;它会像流复制客户端一样接收 WAL 段
docker exec -d -u postgres pg-wal-lab bash -lc '
  rm -rf /tmp/wal-copy && mkdir -p /tmp/wal-copy
  exec pg_receivewal \
    --directory=/tmp/wal-copy \
    --slot=walshadow_lab \
    --verbose
'

# 生成插入、更新和删除记录
docker exec -i pg-wal-lab psql -U postgres <<'SQL'
INSERT INTO events (event_type, payload)
VALUES
  ('signup', '{"user_id": 101}'),
  ('purchase', '{"user_id": 101, "amount": 42.50}');

UPDATE events
SET payload = payload || '{"source": "newsletter"}'::jsonb
WHERE event_type = 'signup';

DELETE FROM events WHERE event_type = 'purchase';
SELECT pg_switch_wal();
SQL

sleep 2

echo 'Received WAL files:'
docker exec pg-wal-lab bash -lc 'ls -lh /tmp/wal-copy'

实验输出只是 WAL 段文件,不会出现可读的 JSON 变更事件。这正是物理 WAL 解析器与普通逻辑 CDC 消费者的根本区别:前者必须从恢复日志中重建行级含义。

测试结束后可清理环境:

docker rm -f pg-wal-lab

上线前应重点验证什么

如果要评估 WalShadow,建议不要只做一次全量导入和吞吐测试。准备一张包含普通列、宽字段、JSON、可空字段和主键的测试表,然后覆盖以下场景:

  • 单事务内连续多次更新同一行;
  • 插入后回滚,以及保存点回滚;
  • 大字段触发 TOAST 后再更新;
  • 删除后使用同一个业务键重新插入;
  • ALTER TABLE 新增列或修改默认值;
  • PostgreSQL 重启、网络中断和消费者重启;
  • ClickHouse 写入成功后,在复制进度落盘前强制终止进程;
  • 长时间暂停消费者,观察复制槽造成的 WAL 保留。

可以用下面的 SQL 监控物理复制槽是否活跃,以及它保留了多少 WAL。将槽名改成实际部署使用的名称:

SELECT
    slot_name,
    slot_type,
    active,
    restart_lsn,
    pg_size_pretty(
        pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)::bigint
    ) AS retained_wal
FROM pg_replication_slots
WHERE slot_name = 'walshadow_lab'
  AND restart_lsn IS NOT NULL;

复制槽能保护尚未消费的 WAL,但也可能在消费者停机时持续占用磁盘。生产环境应同时设置磁盘告警、复制延迟告警和故障处置流程,不能把槽本身当成无限容量的消息队列。

采用建议:把兼容性和正确性放在吞吐量前面

WalShadow 的吸引力在于它把 PostgreSQL 的底层变更流直接连接到 ClickHouse 的原生批量写入模型。但越靠近存储层,接口越可能受到数据库内部实现与版本变化影响。

评估时至少确认四件事:

  • 当前使用的 PostgreSQL 主版本是否被明确支持;
  • UPDATE、DELETE、回滚和 DDL 的映射语义是否符合业务需求;
  • 复制进度与 ClickHouse 写入之间如何实现幂等或故障恢复;
  • 消费者长时间不可用时,如何限制 WAL 保留并安全重建同步。

对追求高吞吐分析复制、并有能力进行严格版本验证的团队,这是一条值得测试的路径。对更重视稳定逻辑协议、跨版本兼容和成熟生态的系统,逻辑解码仍然是更保守的默认选择。真正的判断标准不是方案够不够新,而是故障发生后,团队能否证明 ClickHouse 中的数据仍然完整、可解释并且可以恢复。


相关推荐