返回

Databricks详解LTAP架构:驳斥四大常见误解

RSS Databricks Blog AI 逐段翻译 发布 2026-11-13 05:49

有效阅读仅保存在当前浏览器,用于「为你」推荐;暂停或清除记录。

DataHot 速览

Databricks工程师撰文回应外界对Reynold Xin提出的LTAP架构的四大常见误解:LTAP并非CDC复制、单份数据并非只能一种物理布局、Iceberg/Delta表并非天然陈旧、Postgres逻辑复制并非必需的抽取路径。文章强调Lakebase只在湖中保留每张表的一份持久副本,前端各层仅缓存部分字节以提升耐久性与速度,不构成第二个数据库,因此不会产生目录分裂与数据漂移。

为什么值得关注:LTAP是湖仓与实时分析融合的前沿架构命题,此文第一方澄清了CDC、单副本物理布局等关键概念边界,对做数据平台架构选型的从业者有直接参考价值。

译文

AI 逐段翻译

Reynold Xin 的文章《从单体到 Lakebase 再到 LTAP》解释了什么是 LTAP,以及 Lakebase 架构如何使其成为可能:一份受治理的操作数据副本,同时服务于 Postgres 和分析,无需 CDC 管道,也无需第二个数据库。

随后的许多评论都通过人们已经熟悉的系统来解读这一说法。有四种解读经常出现:

  • “LTAP 只是换了名字的 CDC”
  • “数据的单一副本只能有一种物理布局,因此要么点查询慢,要么扫描慢”
  • “对象存储上的 Iceberg 或 Delta 表天生就是过时的”
  • “Postgres 已经提供了提取路径:逻辑复制”

这些误解每一个都建立在关于 Lakebase 如何存储数据的错误假设之上。让我们逐一来看。

误解 1:“LTAP 是复制”

实际情况:LTAP 在湖中为每张表保留一份持久副本。它前面的各层持有部分相同的字节,以使该表持久且快速,但它们都不是第二个数据库。

从外部看,LTAP 可能看起来像一条熟悉的管道。Postgres 写入行,一个进程跟踪日志,更改落入 Iceberg 表,分析读取 Iceberg。那条管道就是 CDC。它也是逻辑复制、镜像以及大多数零 ETL 产品的结构。因此很容易将 LTAP 归类为相同的设计。

要理解为什么它不是,需要精确界定什么才算第二份副本。一个系统最终在不止一个地方持有相同数据,有两种不同的方式:

  • 复制通过将更改从一个数据库传送到另一个数据库来保持两个数据库对齐。每一侧都有自己的目录和自己的时间线,任何一侧都可以作为事实来源被查询。如果传送停滞,两份副本就会偏离,而且没有任何机制强制它们重新一致。
  • 分层保持一个数据库,并在其前面放置一叠缓存。缓存持有部分相同的字节,以便读取快速。如果读取未命中缓存,就向下走一层,付出一些延迟代价。它从不创建第二个目录,而且如果缓存丢失,表不会丢失。

所以测试很简单:这一层能否作为自己的数据库被查询或故障转移?如果可以,它就是副本。如果不行,它就是一层。

按照这个测试,Postgres 一直是分层的。WAL、缓冲区缓存和索引都存储与堆重叠的字节。没有人称索引为表的副本,因为它没有自己的目录或时间线。

LTAP 保持同样的层次结构。它将其移到对象存储上,并让湖仓引擎读取底部持久层。

以下是 Lakebase 中该层次结构的样子,跟随一次写入然后一次读取:

  • 当写入的 WAL 到达 safekeeper 的法定人数时,该写入就是持久的。然后 pageserver 应用日志,将热页保留在内存和本地磁盘上,并将数据物化到对象存储中。
  • 读取从计算节点开始,该节点保留缓冲区缓存和本地文件缓存。索引、TOAST 和可见性映射位于同一热路径上。在那里未缓存的页来自 pageserver。

对象存储本身也以相同方式工作。当你将对象 PUT 到 S3 时,它会被存储不止一次,通常使用纠删码跨可用区存储。API 仍然命名一个对象,副本之间没有同步作业运行。Lakebase 将同样的理念堆叠在湖前面:日志、缓存、pageserver、对象存储。额外的文件是一张表变得持久且快速的方式。

CDC 确实创建了第二个数据库。它将逻辑更改流解码为外部模式,通常以主键为键,并 upsert 到源永远不会读回的表。该第二个表可以单独查询,并且可能与源脱节:类型可能漂移,DDL 可能丢失,复制槽可能丢失行,而没有人选择的表永远不会出现。结果是两个目录。

LTAP 没有第二个表需要保持同步。没有需要选择加入的表列表。Postgres 中存在的表就存在于湖中,而主库提供的页是该存储的缓存。

误解 2:“单一副本意味着查找慢或扫描慢”

实际情况:同一行的两种无损编码可以服务于不同的访问路径——用于点查询的堆页,用于扫描的 Parquet。每个查询读取对其快速的布局。

即使额外文件是一个层次结构,布局仍然是一个单独的问题。点查询在行上高效,扫描在列上高效。如果磁盘上只有一种编码,其中一种访问模式就会在 I/O 上付出代价。将表称为单一逻辑副本并不会改变这一点。

这个推理是正确的,但它建立在一个假设之上——一份副本意味着一种编码。事实并非如此,只要两种格式能够无损地表示相同的行。

所以问题是 LTAP 的两种格式是否无损:

  • 主要格式是 Postgres 堆页,非常适合点查询和更新。
  • 第二种格式为分析访问而构建:Delta 或 Iceberg 背后的 Parquet。值被保留,文件携带足够的物理元数据(堆块、偏移量和 MVCC 版本),以便可以重建原始页。Parquet 无法无损表示的值不会被强制转换或丢弃;它们在同一表中的溢出字段中传递。

因为没有任何数据丢失,列式副本可以作为湖中的持久存储,而堆页面则成为它的缓存。这样每条访问路径都会读取适合它的格式。点查会命中计算节点、本地磁盘和页面服务器上的 8 KB 页面。扫描则通过 OLAP 引擎读取 Parquet。索引仍保留在热层;它们不会被转码为 Parquet。Postgres 本来就是这么工作的。它为不同访问路径在同一批行上维护两种物理结构:堆和覆盖索引。没有人把索引当作客户的 CDC 副本。SELECT * FROM t WHERE id = 1 和 SELECT count(*) FROM t 是同一张表上的两条访问路径。

一个范围更窄的观察是成立的。Postgres 生成的列式文件按堆顺序排列:行按页号顺序存放,因为页是每次压缩时重写的单位。这种布局对于重建页面以及只重写脏页很高效。但对于 WHERE order_date >= … 来说,它是一个糟糕的聚簇键。如果仓库查询需要这种聚簇,可以像 银表 已经可以从任何操作源派生那样来派生它。不再需要的是那种以创建湖副本为目的的 CDC 管道。

误区 3:“开放表格式意味着最终一致性”

实际情况:每个分析查询都读取一致的快照。尚未到达湖的提交会从页面服务器读取,因此查询开始前已提交的任何内容都不会被遗漏。

Iceberg 和 Delta 在对象存储上提供快照隔离,而对象存储具有高延迟。因此很容易得出这样的结论:分析总是滞后的,许多小表无法保持新鲜,跨表连接无法保持一致。这种描述符合将 CDC 摄取到湖中的情况。

这取决于一个假设:查询只能看到已经落到对象存储中的内容。在 LTAP 中,查询还可以看到尚未进入对象存储的页面。这改变了答案,而跟随一个查询来看是最容易理解的。

当一个分析查询开始时,

  1. 它向 Postgres 请求当前的 LSN。这是一次元数据查找。
  2. 它从对象存储读取几乎全部数据:截至该 LSN 已经物化的所有内容。不与 OLTP 节点共享 CPU 的引擎会执行这次读取。
  3. 它从页面服务器合并其余部分。在上次物化之后提交的页面来自页面服务器,并被合并到顶层。

Postgres 不承担任何批量读取流量——它返回一个数字,最多再返回那些最近的页面。湖不需要包含最新提交,该提交也能可见。它需要包含几乎所有内容,外加一种获取在上次物化之后提交的页面的方法。LSN 就是截止点:它告诉引擎合并需要推进到多远。

同一个 LSN 还能保持连接一致。CDC 逐表应用,因此一个关系可能比另一个处于更晚的槽位,连接会混合两个时间点。LTAP 在单个 LSN 上读取每张表,这与 Postgres 用于 REPEATABLE READ 的同类截止点相同,而不是读取各管道最后刷新的任意位置。该 LSN 是一个共享水位线。用于读取自水位线以来最近变更的 API 正在开发中,既有为 Lakebase 原生提供的,也有作为上游 Postgres 扩展提供的。

小表是元数据成本,而不是新鲜度限制。Iceberg 的开销在于文件和快照。对于只有几行的关系,这种簿记的成本超过其收益,而且布局对扫描性能无关紧要。这些表保留在页缓存中,参与 LSN 一致性读取,并跳过 Iceberg 提交。

这留下了两个听起来相似但实则不同的说法:

  • "长查询看不到新提交"——正确,而且对。从 LSN X 开始的查询不会看到 X 之后的提交。这是快照隔离,与 REPEATABLE READ 相同的契约,而不是副本延迟。
  • "查询遗漏了已提交数据"——这将是一个错误结果:一个从 X 开始的查询遗漏了在 X 时已经持久化的提交,因为它仍停留在 CDC 缓冲区中。按该 LSN 合并最近的页面正是 LTAP 避免这种情况的方式。

物化的列式快照可能比主库滞后最近一个压缩间隔,但查询本身不会。Parquet 文件可能滞后几分钟,这并不意味着读取器无法看到已提交的数据。

误区 4:“我们只需要 Postgres 复制”

实际情况:逻辑复制是一种让湖与数据库保持同步的高成本方式。它逐行重放每个变更,并且需要一条必须跟得上的管道。并行解码 WAL 并不能绕过这一点,因为日志记录无法独立处理。

在新鲜的 Postgres 上进行分析的标准方式是启用逻辑复制。Debezium、PeerDB、pgoutput 及类似工具从这样一个观察出发:数据库已经发出变更流,然后问还需要什么存储格式。

问题是变更流是否是构建湖中表的好方式。变更流的两个属性表明它不是:它必须读取多少,以及它必须携带多少状态。

逻辑复制读取每一个变更

WAL 是页编辑的物理日志,写入它是为了让 Postgres 能够崩溃并恢复。逻辑解码将其转换为按提交顺序排列的行事件。这种转换无法将同一页上的多次更新折叠为页的当前镜像;每一次变更都必须经过。在一个热页上,Postgres 在两次检查点之间可能将相同的 8 KB 记录一百次。逻辑解码会发出一百个事件,然后下游作业按主键将它们 upsert 到 Parquet 中。相比之下,页面分层路径只读取该页一次。在我们测量过的写密集型工作负载中,CDC 至少读取与 WAL 等量的数据,而脏页压缩可能只读取其中一小部分。

逻辑复制携带着状态

复制槽是 WAL 中的一个游标,消费者必须持续将其向前推进。如果消费者落后,WAL 就会留在磁盘上,直到该槽被删除或磁盘被写满。逻辑解码不发出 DDL,因此 ALTER TABLE、类型变更和重命名要么使事件与当前目录不一致,要么使该槽失效。没有主键的表需要 REPLICA IDENTITY FULL。由于该管道开销高昂,它通常只应用于一部分表。这与“每个 Postgres 表因为存在而出现在湖中”正好相反。

一个相关的说法:可以跳过逻辑解码,并行解码 WAL

页面服务器已经在消费 WAL,因此一条并行的日志到 Parquet 路径看起来像是额外的一小步。这在原则上可行。但它尚未被构建为生产级的提取器,也不是 LTAP 所采用的路径。WAL 记录是有序的,因为后面的记录会假定前面的记录已存在。可见性存在于该 LSN 处的 pg_xact、proc 数组和目录中,而不是在元组中。独立的工作者无法各自解码一个不相交的字节范围并发出正确的当前行。

LTAP 并行化的是页面,而不是日志记录。该系统询问自上次快照以来哪些页面发生了变化(Postgres 17+ 上的 WAL 摘要,或页面服务器的日志结构合并树),每个脏页都是独立的工作。该集合被分区,页面被转码,并提交一个 Iceberg 快照。日志是写入得以持久化的方式,也是发现哪些页面发生了变化的一种方式。快照是当前页面,而不是对使它们变脏的每条记录的重放。

这一切都不意味着复制对于其预期任务(迁移、大版本升级以及切换到第二个 Postgres)是错误的工具。但将逻辑复制用作存储会产生第二个事实来源。

结语

所有四个迷思都源于同一个误解:将数据的每一种物化都视为第二个数据库。

LTAP 维护一个逻辑表,而湖是其持久副本。堆页面、索引和缓存是 Postgres 以 OLTP 延迟服务该表的方式,就像覆盖索引服务于对堆的查询一样。OLAP 引擎直接扫描 Parquet,并合并自上次物化以来事务一致的变化。

这些层都不会增加第二个目录或第二个可能发生漂移的事实来源。

要深入了解 Lakebase 的存储架构如何引出 LTAP,请参阅这篇文章。

这篇内容对你有用吗?

反馈只用于改善内容筛选,不等同于收藏

分享这条资讯
分享海报
保存图片
iOS 也可以长按图片保存