本文深入探讨数据镜像背后的工程实现:我们如何从头重新构想 Postgres 复制。
Postgres 是一款出色的操作型数据库,但其变更数据捕获(CDC)能力仍有很大提升空间。许多管道最终变得脆弱,因为复制工具需要处理连续数据与 schema 变更、快照和失败之间复杂的相互作用。为了给 Snowflake Postgres 构建可靠、开箱即用的体验,我们必须从头重新发明 Postgres 复制。
数据镜像是 Snowflake Postgres 的一项新功能(公开预览),以低成本、低延迟和事务一致性将数据高度弹性地复制到 Snowflake。其底层原理是:将变更以事务性批次直接从 Postgres 推入 Apache Iceberg™ 表。这些批次会自动以事务性、无服务器的方式应用到 Snowflake 的表中。
“事务性推入数据湖、事务性应用到 Snowflake、无需额外基础设施”这一简洁性,将复制从充满复杂故障条件的混乱过程,变成一台能永远运行的简单时钟。
你只需按下一个按钮,就能在 Snowflake 中获得你的 Postgres 表。
变更数据捕获是指从事务性数据库中捕获变更,并以可在另一系统上重放的形式呈现的过程。
在 Postgres 中,主要机制称为“逻辑解码”(logical decoding),即将 WAL 记录解码为逻辑上的行级插入/更新/删除操作。这些操作以流的形式通过网络暴露。从那时起,负担就落在了客户端身上。
在实践中,复制涉及更多步骤:回填、schema 变更、处理建表/加表/删表/丢表操作、新表快照、失败重启、高效合并变更、保留事务边界、合理扩容等等。即使是 Postgres 内置的逻辑复制,也只处理了其中一部分。
逻辑解码方法的一个问题是,消费变更的外部系统对 Postgres 的状态一无所知。例如,它不知道 schema 何时变更、表快照如何与变更对齐——甚至不知道 Postgres 是否存活,还是只是网络断了。
这个问题的解决方案非常简单:将变更从 Postgres 推入数据湖,在我们的场景中就是推入 Iceberg 表(使用压缩 Parquet)。像 Amazon S3 这样的对象存储具有高度可扩展性和可靠性,并且一直用于 Postgres 备份。它也是变更数据捕获的正确目的地。
镜像功能使用一个名为 snowflake_cdc 的新 Postgres 扩展,持续将变更批次推入每表的变更日志(change log)和后台的“元日志”(meta log)(使用“基础工作进程”)。使用扩展的好处是,它确切知道 Postgres 内部正在发生什么。它可以仔细协调 schema 变更以及复杂的 DML 和 DDL 事务。它可以在推送变更的同时拍摄快照,并将快照与变更对齐。
基于推送的变更数据捕获避免了一整类基础设施及相关问题,并通过对象存储有效地将生产者和消费者解耦。
在构建像数据镜像这样的复制系统时,一个重要的方面是数据库的时间线。复制进程处理的是数据库在最近过去时间点的状态。
对 Postgres 的每次写入实际上都经过四个阶段,每个阶段代表一个在同一时间线上运行、但处于不同时间点的连续过程:
解码进程依赖 Postgres 中的特殊机制,读取写入发生时目录表的状态(“历史快照”)。这样,即使表在记录被解码时已被更改或删除,二进制的 WAL 记录也能被理解为逻辑行变更。在数据镜像的场景中,记录会进入临时文件。
解码器会定期收到信号,完成当前批次并向捕获进程发送批次就绪的消息。捕获进程将 finalized 文件追加到 Iceberg 变更日志中,在元日志中写入一条记录,并跟踪已复制的 LSN。Schema 变更遵循相同的 写入 → 解码 → 捕获 路径,并可能产生新的变更日志。
新的元日志和变更日志记录出现在 Iceberg 表中。然后,Snowflake 中的应用进程充当一个有限状态机,执行元日志中的所有指令。当操作是变更批次(常见情况)时,每个表的所有相邻变更批次会被一起处理。
这种方法有助于确保 schema 变更被正确排序到变更流中,即使它们发生在包含额外写入的事务中也是如此。如果发生意外故障导致 WAL 被丢弃,Postgres 可以自动推送新的快照并指示 Snowflake 消费它们,但由于使用了故障转移槽(failover slots),这种情况在实践中非常罕见。
数据库系统可以通过一个简单的原语隐藏大量与系统和硬件故障相关的复杂性:事务。
如果事务因底层系统故障而失败,什么也不会发生,你可以重试。如果事务成功,你可以确保它永远不会再次执行同样的工作。
我们在处理诸如提取-转换-加载(ETL)或 CDC 之类的流程时所感受到的挫败感,是因为事务突然失效了,我们必须自己处理所有不同的故障模式。
我们对这个问题的第一个答案是 Postgres for your data lake,这是我们开源 pg_lake 扩展 的托管版本——现已全面可用。它赋予 Postgres 在 Postgres 表和 Iceberg 表之间执行事务的独特能力。ETL 通常需要外部工具,用户需要考虑幂等性并进行非常仔细的簿记。现在,你可以直接使用 SQL 从 Postgres 表中删除、插入 Iceberg 表并提交。此时,数据即可在 Snowflake 中查询。这种方法用途广泛,但 SQL 并不适合端到端复制高频更新,因此这正是数据镜像所补充的层次。
在底层,数据镜像充分利用了 pg_lake 和 Snowflake 的 Iceberg 实现。它会在 Postgres 侧的一个事务中,将来自 Postgres 表的数据批次和 schema 变更推送到 Iceberg 中的多个变更日志。然后,Snowflake 在 Snowflake 侧的一个事务中一次合并多个批次。这意味着所有 Snowflake 表都在一个事务中向前推进,精确到一个 Postgres 事务边界,从而保持外键和连接的正确性。
这种事务性复制方法可以扩展到非常高的吞吐量,并避免了传统跨系统复制的大多数常见故障和竞态条件。
事务还启用了另一件重要的事情:大规模下的正确性。
一种常见的复制方法是将每个操作转换为一种 upsert。这样做的原因是,很难解决表快照与变更之间的一致性,以及失败后可能重放多次的变更的一致性问题。然而,upsert 方法存在几个问题:
当我们将复制变成由 Postgres 控制的事务性过程时,我们就没有同样的限制。我们可以生成完美的删除和插入流,并且恰好应用一次,而不会出现插入已经出现在快照或另一个变更批次中的风险。
实时视图(Live views)是数据镜像的一项功能,它将每表变更日志中未应用的变更与目标表中的数据结合起来。重要的是,查询中的任何过滤和投影都可以直接下推到存储层,并对变更日志中的 Parquet 文件和基础表进行表扫描。换句话说:实时视图很快。
借助实时视图,不再需要频繁应用变更来实现低延迟。即使你很少应用,实时视图的延迟仍将远低于一分钟,而且你仍然可以获得高性能查询,只是开销略高。
基于推送的 CDC、围绕时间线的精心设计、两侧的事务边界以及实时视图的组合,意味着镜像将复制从混乱的过程变成了瑞士钟表。没有可能落后的外部连接器。没有可能与变更冲突的快照。没有随着表增长而变慢的 upsert。有一个 Postgres 扩展将批次推入对象存储,Snowflake 应用它们——两者都是事务性的、独立的、可持续的。
从历史上看,你的操作型和分析型工作负载生活在不同的世界中。这些世界过去常常用脆弱的管道和增加成本的额外系统缝合在一起,其中还夹杂着无尽的痛苦和折磨。我们的重点是,通过简单而精心设计的解决方案,在两侧夯实基础,统一这些世界。
借助 Snowflake Postgres,你拥有值得信赖的生产级 Postgres,现在还有两种方式可以统一你的工作负载,提供了灵活性:
如果你准备好开始,可以查看以下链接:
每周将最棒、最酷、最新的内容发送到你的收件箱
提交此表单,即表示我了解 Snowflake 将根据其隐私声明处理我的个人信息。
随时了解 Snowflake 的最新产品、专家见解和资源——直接发送到你的收件箱!
[ LinkedIn ] [ Facebook ] [ YouTube ]
(此处省略页脚样式和页面配置等与正文无关的代码)