返回
RSS Databricks Blog AI 逐段翻译 发布 2026-08-27 15:18 收录于 08-28

对象存储+WAL:面向智能体时代的Lakebase Postgres

DataHot 速览

传统OLTP数据库在存储层成为Agent工作负载的瓶颈,复制、恢复和副本操作都需移动大量数据,成本高昂。对象存储则廉价、高性能且几乎免运维。Lakebase Postgres将对象存储置于事务数据库之下,利用Postgres的WAL记录所有事务操作,把数据库视为操作时间线而非当前状态快照,从而让Agent更容易获得隔离副本、回滚和并行环境。

为什么值得关注:数据从业者可从中看到面向Agent负载的新型数据库存储架构思路,对湖仓与事务数据库的融合实践有参考价值。

本文目录 20 节
  1. 两种 OLTP 模型
  2. WAL 中的写入
  3. 日志成为事实来源
  4. 计算层
  5. 存储层
  6. 写入路径
  7. 读取路径
  8. 非覆盖存储
  9. 如何快速找到正确的层
  10. 第一步:针对单个LSN解决
  11. 第二步:使树持久化
  12. 对象存储的实际位置
  13. 为什么使用Lakebase Postgres而不是标准Postgres
  14. 分支
  15. 即时恢复
  16. 时间旅行查询
  17. 无需副本的只读副本
  18. 缩放到零
  19. 事务和分析的一个副本
  20. 适用于智能体的Lakebase Postgres

译文

AI 逐段翻译

与传统OLTP数据库交互的代理通常会在存储层造成瓶颈。新的部署、复制、恢复和副本都意味着移动大量数据,这既耗时又昂贵。

对象存储则截然相反。例如,Amazon S3 价格低廉、性能优越、几乎无需运维。它为代理记忆创建了一个可扩展、成本高效的存储层。

这引出了一个问题:对象存储能否置于事务性数据库之下,使代理更容易与之协作?

这个问题正是 Lakebase Postgres 的起点。答案不仅取决于你的对象存储有多快,更在于你将事实来源置于何处。

两种 OLTP 模型

通常对 OLTP 的心理模型是以数据为中心的。数据被组织成带有行和列的表,每个表代表一个实体。存储是当前状态所在的位置,数据库的工作是存储和检索它。

但还有第二种模型:以事务为中心。这里数据库是一本事务日志。每个条目是一个操作,存储是这些操作的时间线,而不是当前状态的快照。当前状态是你可以从时间线推导出的一种东西。

多年来,以数据为中心的模型在实践中是唯一重要的,因为运维团队对数据库的要求是对当前状态的读写。在过去几年中,情况发生了巨大变化。代理工作负载要求的操作几乎都是对事务历史的操作:

  • 给我一个隔离的生产副本用于工作
  • 把它恢复到我的最后三条语句之前的状态
  • 显示迁移前这个表的样子
  • 同时运行二十个这样的操作,并在一个小时内删除其中十九个

这些都是关于时间线的查询。只存储当前状态的数据库提供副本和备份,这些既慢又昂贵。

然而,Postgres 已经包含了这个时间线:它被称为预写日志(WAL)。

WAL 中的写入

Postgres 的 WAL 在修改到达数据文件之前记录每一次修改。它最初存在是为了让 Postgres 能够恢复:如果服务器在日志写入和数据文件写入之间崩溃,WAL 重放可以弥补这一间隙。

但 WAL 的内容远不止于恢复。以一个表和一次插入为例:

在更改到达users磁盘上的表之前,Postgres 将其追加到 WAL。日志是二进制的,但pg_waldump可以将其渲染。这次插入的记录大致如下:

这是四条记录和一个事务。注意每条记录都有一个日志序列号(LSN),这是一个单调递增的标识符。

堆和 btree 行还命名了确切更改的 8 KB 页面。日志不会说“添加了一行”。它说在时间线上的哪个点,哪个关系中的哪个页面。

将其读作恢复机制,它是在崩溃后需要重做的工作列表。但如果你将其读作事务日志,它就是另一种东西:对数据库曾经更改过的每个页面的完整、有序、字节级记录,每个条目都有唯一的名称。

这个名称——LSN——是最重要的部分。它意味着时间线已经是可寻址的。无需向 Postgres 添加任何内容即可使“某个时间点的数据库”成为定义明确的事物。它只需要一个存储层来保留日志并能回答针对它的问题。

日志成为事实来源

在传统的 Postgres 部署中,WAL 是一种达到目的的手段。数据文件是数据库,日志保护它们,一旦记录安全应用,日志就会被截断。存储只是连接到运行 Postgres 的机器上的一个磁盘,数据库身份的一切都与那台机器相关。

现在,让我们反转它。把日志当作数据库,数据文件是它的派生、缓存的表示。然后你可以保留完整的时间线,并且不再需要移动数据来复制或回滚数据库。历史变得可寻址,因此数据库“副本”变成了一个指针,而不是一组第二个文件。这使得部署、恢复和副本变得像代码一样廉价。

这就是我们在 Lakebase Postgres 中所做的。具体来说,我们将系统分为两层:

计算层

计算层运行标准 Postgres。它解析 SQL,规划和执行查询,强制执行 MVCC,管理锁和索引。

查询引擎中没有任何内容被重写。改变的是计算节点的职责:它存在是为了执行工作,而不是保存数据。它有用于共享缓冲区的 RAM 和用作页面缓存的本地 NVMe,并且可以在任何时刻启动、停止、扩展或消亡,而不会危及持久性。

存储层

存储层负责正确性、持久性和历史。它比任何单个计算节点都长寿,它由三个具有不同职责的组件构建:

  • Safekeepers 复制 WAL。当计算节点生成 WAL 记录时,它会将它们流式传输到多个 safekeeper,并且当事务通过基于 Paxos 的协议得到多数确认时,事务即提交。持久性是复制和共识的属性,而不是单台机器的fsync
  • pageserver 将 WAL 转换为页面。它结合基础页面和已提交的 WAL 记录,以具体化给定查询所需的页面版本,并将这些具体化的版本异步持久化到对象存储中。
  • 对象存储保存长期、不可变的历史。 具体化的页面版本和历史状态被保存为追加式记录,而不是可变文件系统。
image2.png

写入路径

写入路径是什么样子的?在此系统中,提交遵循以下步骤:

  1. Postgres 在内存中应用更改。缓冲区被更新,索引被修改,WAL 记录照常生成。
  2. 计算节点不是将 WAL 刷新到本地文件系统,而是通过网络将其流式传输到 safekeeper。
  3. 一旦法定数量的安全保管人确认了记录,事务即被提交。这是客户端收到成功消息的时刻。
  4. 页面物化随后在存储层进行,处于事务的关键路径之外。提交从不等待页面被写入或上传。
image3.png

这种设计可能会遇到一个明显的反对意见:步骤2在提交路径中增加了一次网络跳转。但任何认真对待持久性的Postgres部署已经在运行同步复制,这也是一次网络跳转。将WAL外部化是用一次网络往返替换另一次,而不是增加一次。

读取路径

来自计算节点的每个读取请求都携带一个页面标识符和一个LSN,存储层返回该页面在该LSN时的状态。这个GetPage@LSN是该架构中的核心操作。

服务该请求有一个优先级顺序:

  1. 首先是Postgres共享缓冲区的内存,与任何Postgres一样。
  2. 然后是本地NVMe,它仍然快速,仍然本地。如果页面不在内存中,计算节点会检查其本地磁盘缓存。
  3. 只有在本地未命中时,请求才会通过网络进入pageserver。pageserver随后检查它是否已经物化了该页面版本。如果没有,它会找到该页面在请求LSN或之前的最近镜像,收集其上的WAL记录,重放它们,并返回重构的页面。

返回的页面随后缓存在内存和NVMe中,因此下一次读取又变为本地操作。

image4.png

主节点请求每个页面的最新版本,因此在稳定状态下,它表现得像任何从热缓存读取的Postgres。但协议中没有任何内容要求“最新”。请求四小时前LSN的页面,你会得到四小时前的页面。

有用的结果是,实时数据与历史备份之间的区别消失了。只有一个存储系统。旧页面版本不是存储在别处的独立工件,格式不同;它们是相同的不可变文件,仍然可寻址。

非覆盖存储

换句话说,pageserver永远不会就地更新文件。文件被创建、合并和删除,但从不修改。这完美契合对象存储,因为对象存储不支持随机更新,并且这使历史数据变得足够便宜以保留。

数据组织为两种层文件:

  • 镜像层保存某个LSN处键范围内所有键的快照。
  • 增量层保存键和LSN范围内的所有更改。未被修改的键不会被存储。传入的WAL被写入为增量层。

镜像层在后台生成,原因有二:它们缩短了读取需要遍历的重放链,并使旧的增量可被收集。没有它们,重构一个页面可能需要回溯任意远。

所以GetPage@LSN变为一次搜索:从请求的键和LSN开始,向下遍历层收集该页面的WAL记录,并在第一个镜像处停止。为了保持搜索简短,增量和镜像层通过后台压缩重新整理,而超出保留窗口的层被垃圾回收。

如何快速找到正确的层

上述搜索听起来简单,但事实上并不简单。这值得花时间研究,因为它决定了整个设计是否可行。

一次读取指定一个键和一个LSN。存储系统必须找到覆盖该键在该LSN或之前的最近层。这是一个几何问题,并且不清楚如何在数千万层中解决它。线性扫描太慢,而明显的空间结构不适用:R树回答包含查询而不是“这个点下方的第一层”,线段树随坐标空间的大小缩放,而不是随层数缩放。

有几种方法可以实现这种设计,但有效的方法是先解决容易的问题,然后让数据结构记住自己的过去。

第一步:针对单个LSN解决

对于一个固定的LSN,我们确定哪些层为每个键提供答案。这个答案只会在键空间中的少数几个点发生变化,因此我们记录这些点并将它们存储在二叉搜索树中。这棵树是该LSN的层覆盖,并且可以在一次查找中回答该LSN任何读取。

这有效,但仅适用于一个LSN。层每次添加时覆盖都会变化,并且有数百万个LSN,因此我们无法为每个LSN构建并保留单独的树。

第二步:使树持久化

持久化是指“保留旧版本可用”。我们按LSN顺序自底向上增量构建覆盖。插入一个层只触及从根向下的单一路径上的节点。系统不是覆盖这些节点,而是复制它们并保留原始节点不变。新副本指向两侧未改变的子树。

由此产生两点:

  • 插入成本是少数新节点,而不是一棵全新的树,因为路径之外的所有内容都是共享的。
  • 旧的根仍然精确描述插入之前的树,因此它仍然是较早LSN的有效覆盖。

我们对每一层按顺序执行此操作,最终得到一个包含所有中间根的结构,每个根对应不同LSN的覆盖。我们以接近一棵树的代价获得了所有这些树。

历史读取因此与当前读取成本相同:系统选择您想要的LSN对应的根,并执行相同的单次查找。

这就是技巧,总结如下:

  • 仅最新读取是一次树查找。
  • 历史读取使用较老的根,因此成本相同。
  • 随着层累积,构建这些根保持廉价,因此长历史不会使查找变慢。

对象存储的实际位置

这是当前关于Postgres和对象存储的争论往往在两个方向上出错的地方。

反对在对象存储上构建OLTP的经典论点如下:

  • Postgres处理许多小的、对延迟敏感的I/O。
  • 对象存储是为更大请求和更高延迟而构建的,从中读取数据可能需要数百毫秒。
  • 如果在查询执行前面加上S3,结果就是一个缓慢的数据库。

这本身并不是一个有争议的说法。这个论点的错误在于假设基于对象存储的数据库必须从对象存储中读取数据来回答查询。

在我们提出的架构中,它从不这样做:

  • 查询不读取对象存储。计算节点读取RAM,然后是本地NVMe,然后是pageserver。对象存储仅在pageserver内部读取,且仅当重建其没有的页面版本时,Postgres从不直接读取。
  • 提交不写入对象存储。当多数派safekeepers拥有WAL记录时,提交就被确认,物化页面和上传操作随后进行。

当Postgres以这种方式构建时,它就成为传统OLTP系统的演进,旨在处理智能体工作负载。这就是我们创建Lakebase Postgres的原因:一个计算和存储分离的OLTP数据库,其持久化的真相来源基于对象存储。

为什么使用Lakebase Postgres而不是标准Postgres

使用Lakebase Postgres,事务历史可以通过LSN寻址,副本是引用而非数据。这使得构建为Postgres提供轻量级工作流的功能成为可能,而这对于智能体来说是绝对必要的。

分支

首先,Postgres现在可以分支了。创建分支不会复制页面,而是创建指向特定LSN的指针,分支从那里以写时复制语义开始分叉。

对分支的写入存储为相对于父分支的增量,因此2 TB数据库的分支在几秒钟内创建,在更改之前不花费任何费用。父分支不会看到额外的负载,这就是为什么在生产环境上这样做是安全的。

这是智能体安全工作需要的东西。它可以为每个任务创建一个分支,在真实数据和真实规模上运行刚刚编写的迁移,并在任何东西触及父分支之前检查结果。二十个智能体可以同时这样做,每个都相互隔离并与生产隔离。

使用Lakebase Postgres,我们甚至扩展了分支超越了数据库。对象存储桶、函数、托管Better Auth状态和AI网关配置与数据库一起分支,因此分支是后端的隔离副本,而不仅仅是Postgres表。

即时恢复

时间点恢复是一种不同目的的分支。恢复意味着指向较早的LSN并从那里继续,因此不涉及将数据复制回原位,其成本不随数据库大小扩展。你能回溯多远是一个保留设置

这使智能体的错误变得廉价。当智能体运行错误语句时,答案不是恢复窗口和恢复计划,而是将分支指回运行之前的LSN。在2 TB数据库上撤销的成本与空数据库相同,因此智能体可以重试而不是升级到人工处理。

时间旅行查询

因为pageserver可以在历史窗口内重建任何LSN处的任何页面,你可以直接查询过去的状态,而不是先恢复它。

实际用途是比较差异:这个表在迁移前是什么样子,现在是什么样子。这也是你确认在提交恢复之前选择了正确时间戳的方法。

无需副本的只读副本

只读计算节点不是数据的副本。它从与主节点相同的存储层请求页面,因此添加一个只读节点不需要预置数据集并等待其赶上。启动一个只读节点是一个元数据操作。

缩放到零

由于持久化状态位于计算之外,空闲的计算节点可以完全关闭,而不是为了保护数据而保持运行。计算节点在5分钟不活动后暂停,并在下次查询时在几百毫秒内重新激活。对于大量的按会话或按分支数据库,其中大部分大部分时间处于空闲状态,这是可行成本模型和不可行成本模型之间的区别。请注意,暂停期间计算停止计费;存储继续计费,因为历史仍然存在。

一个智能体会话工作四分钟后变得安静,五分钟后停止产生计算成本,无需任何人拆除它。这就是使每个智能体、每个会话或每个分支的数据库足够便宜成为默认选择的原因。

事务和分析的一个副本

将运营数据放入对象存储还有另一个后果

一旦事务数据库的持久化记录存在于商品对象存储中,它就不再被锁定在一个引擎的私有格式和磁盘上。其他引擎可以读取它。

这是我们称之为LTAP(湖事务/分析处理)的基础:不是以两种格式保存两份数据并通过管道保持同步,而是以开放的列式格式持久化一份数据,事务和分析双方都读取它。

该机制遵循已描述的读取路径。当pageserver将页面物化到对象存储时,它将其从Postgres行格式转码为列式格式,保留每个值的精确Postgres表示。分析查询要求Postgres提供当前LSN(这是一个廉价的元数据查找),从对象存储中读取截至该LSN的绝大部分数据,并从pageserver获取仅最近未物化的更改。除了返回这一个数字外,Postgres不提供任何分析读取流量,因此大型分析查询不会与事务竞争同一CPU。

与变更数据捕获(CDC)和镜像的区别在于没有需要选择加入的内容。没有复制表列表,因为没有复制。一张表已经存在于湖中,这也意味着两个视图不能漂移。

适用于智能体的Lakebase Postgres

我们在这篇文章开头提出了一个问题:对象存储能否位于Postgres之下,让智能体更容易与之协作?

答案是肯定的。对象存储可以位于Postgres之下,并改变你与之交互的方式,但这不仅仅是因为S3运行速度快或成本低。正如本文所述,这需要的远不止工程上的简单叠加。RAM和本地NVMe仍然是快速服务查询所必需的,而提交仍然落在复制的WAL上,而不是落在存储桶中。

WAL这个环节是关键。对象存储提供了一种廉价且可扩展的方式来存储所有历史记录,但让WAL成为真相来源才是使该历史记录可寻址的关键,并改变智能体与Postgres交互的方式以及你可以在其上构建的功能。

让您的智能体部署Lakebase Postgres并测试它。在此处开始

Lakebase Postgres可以用作独立数据库,您还可以将其与Databricks Data + AI平台的其他部分集成:Unity Catalog治理、湖仓分析、笔记本和AI工作流。

这篇内容对你有用吗?

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

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