来源:https://thebuild.com/blog/2026/05/08/pglake-vs-lakebase-two-very-different-things-called-postgres-lakehouse/
pg_lake vs Lakebase:两种截然不同的“Postgres + 数据湖仓”
2026-05-08
PostgreSQL 相关
Snowflake 和 Databricks 现在都在销售所谓的 PostgreSQL,两者都针对相同的通用用例(“数据湖仓旁边的操作型数据库”),并且产品名称中都带有“lake”一词。Snowflake 将其产品称为 pg_lake(一组开源扩展,加上包装它们的托管式 Snowflake Postgres 服务)。Databricks 将其产品称为 Lakebase(一项基于他们在 2025 年收购的 Neon 引擎构建的托管服务)。
两者的营销话术非常相似,以至于人们很容易认为它们的架构也相似。但事实并非如此。实际上,它们几乎是对立的——一个保持 PostgreSQL 原样,并在其旁边嫁接一个数据湖仓访问层;另一个保留 PostgreSQL 的编程接口,但完全替换了存储子系统。哪一个适合给定的工作负载,取决于营销材料愉快地忽略掉的细节。
本文将拆解它们,探讨查询实际在哪里执行,PostgreSQL 的事务模型还有哪些保留,以及它们在何种有意义的层面上不再是 PostgreSQL。
简短版本
pg_lake是向外扩展的 PostgreSQL。Postgres 仍然是您所熟知的那个权威的、未修改二进制文件的 Postgres。堆表仍然存在于堆文件中,MVCC 仍然以一贯的方式工作,WAL 仍然写入本地磁盘。pg_lake 增加的是定义 Iceberg 表(由对象存储中的 Parquet 文件支持)的能力,并能像查询和写入本地表一样操作它们,PostgreSQL 本身充当 Iceberg 的目录。繁重的分析扫描被委托给一个运行 DuckDB 的 sidecar 进程。
Lakebase是替换了存储堆栈的 PostgreSQL。计算节点是一个 Postgres 二进制文件(带有 Neon 的补丁),但其 WAL 通过网络发送到仲裁的安全保管者(safekeeper),其页面来自一个独立的页面服务器(pageserver)进程,该进程根据 WAL 和基础镜像按需物化页面,并以对象存储作为冷存储层。没有本地堆文件。没有传统意义上的pg_wal目录。从 SQL 表面看,它与上游 Postgres 完全相同。从操作表面看,它则大不相同。
您已经可以看出为什么这些是不同的赌注。一个主张:“Postgres 应该接入数据湖仓。”另一个主张:“Postgres 的存储应该是一个具有数据湖仓形态的存储系统。”
pg_lake:Postgres 作为 Iceberg 目录
简化后的架构:
libpq | psql / app | PostgreSQL 后端 (未修改) | | | heap I/O --> 堆文件 (本地磁盘) | | | WAL --> pg_wal (本地磁盘) | | | 目录操作 --> pg_lake 目录 (在 PG 的目录表中) | | | 外部扫描 --> pgduck_server (DuckDB) | | | S3 GET/PUT --> 对象存储 | 清单指针 (Iceberg 表) | | | Parquet用通俗的语言描述各组件:
- PostgreSQL 后端与您今天运行的完全一样。相同的二进制文件、相同的
postgresql.conf、相同的 MVCC、相同的 WAL、相同的一切。堆表、B 树索引、GIN、GiST——全部未变。 - pg_lake 扩展家族是一个由大约十二个扩展组成的堆栈:
pg_lake_table(用于湖文件的 FDW)、pg_lake_iceberg(Iceberg 特定的元数据处理)、pg_lake_engine(共享内部逻辑)、pg_extension_base(基础层),以及像pg_map、pg_extension_updater这样的辅助工具,加上可选的pg_lake_spatial(支持 PostGIS)和pg_lake_benchmark。 - PostgreSQL 本身充当 Iceberg 目录。这是整个设计中最有趣的决策。Iceberg 规范期望一个外部目录(Hive Metastore、AWS Glue、REST catalog、Polaris、Unity Catalog)——pg_lake 让 Postgres 扮演这个角色,将快照指针和清单引用存储在常规的 Postgres 目录表中。在 Iceberg 表上执行
CREATE TABLE会写入 PG 的目录;对象存储中的清单会被更新;整个过程发生在一个 Postgres 事务内部。 - pgduck_server是执行实际分析繁重任务的部分。它是一个独立的、多线程的进程,通过 Unix socket(或端口 5332)使用 PostgreSQL 线协议进行通信,并在底层使用 DuckDB 作为列式执行引擎。当 Postgres 规划器识别出一个查询正在访问 Iceberg 支持的外部表——特别是那些能从列式扫描、谓词下推和并行 S3 读取中受益的表——它会将该扫描委托给
pgduck_server,后者获取相关的 Parquet 文件并返回行。
您从中得到什么:
- 堆表的行为仍然像堆表。OLTP 流量不受影响。您对延迟敏感的
INSERT ... ON CONFLICT与昨天的行为完全相同。 - Iceberg 表的行为类似于外部数据包装器。您可以通过 PG SQL 查询它们,并与您的堆表进行完整的 JOIN。谓词下推是真实的。对 Iceberg 表的更新和删除操作存在,但它们遵循 Iceberg 的写时复制语义——这些行上没有 PG 意义上的 MVCC。
- 跨工具互操作性是主要特性。由于数据以标准的 Iceberg + Parquet 格式存在于对象存储上,Spark、Trino、Athena、独立 DuckDB、ClickHouse 以及任何其他能读取 Iceberg 的工具都可以读取相同的数据。PG 是目录和查询引擎,但它不是这些表的记录存储。
- 事务方面比营销宣传的更微妙。一个同时涉及堆表和 Iceberg 表的事务并非完全跨边界 ACID 的。堆表侧的更改受 MVCC 保护,具有完整的 PostgreSQL 语义。Iceberg 侧的更改在清单指针更新时提交,Iceberg 侧的可见性规则是 Iceberg 的规则——针对表级快照的快照隔离,而不是行级 MVCC。对于大多数分析工作负载来说,这没问题。对于您确实需要两侧严格保证一起失败或提交的工作负载,请在构建之前仔细阅读文档并测试故障模式。
pg_lake 的 PG 兼容性足迹本质上是“完整的 Postgres,加上一个能理解 Iceberg 的外部数据包装器”。如果您能运行扩展,您就能运行 pg_lake。Snowflake Postgres 托管服务运行相同的代码,并附加了围绕目录、治理和捆绑的pgduck_server部署的商业功能。
Lakebase:没有您熟知的存储的 PostgreSQL
简化后的架构:
libpq | psql / app | PostgreSQL 计算节点 (带有 Neon 补丁的二进制文件) | | | 缓冲区读取 --> 本地文件缓存 (在计算节点上) | | | 页面缺失 --> 页面服务器 | (根据 WAL + 基础镜像重建页面) | | | WAL 流 --> 安全保管者 (Paxos 仲裁) | | | 持久化 WAL --> 对象存储 (WAL 归档) | | | 层文件 --> 对象存储 (页面增量 + 基础镜像)用通俗的语言描述各组件:
- 计算节点是真正的 PostgreSQL 后端。它解析 SQL、规划查询、执行查询、管理 MVCC、获取锁、拥有自己的
shared_buffers。它相对于上游有补丁——更改主要发生在存储管理器(smgr)层——但 SQL 表面是相同的。扩展可以安装。pg_stat_statements可以工作。pg_dump可以工作。 - 本地文件缓存(LFC)是位于
shared_buffers和页面服务器之间的额外缓存。概念上:共享缓冲区是热工作集,LFC 是温工作集,页面服务器是真实数据源。调整 LFC 是 Lakebase 性能体验的主要来源。 - 安全保管者是一个 Paxos 风格的仲裁。计算节点不将 WAL 写入本地
pg_wal目录并执行fsync(),而是通过网络发送 WAL 记录。当一个仲裁的安全保管者确认了 WAL 记录后,事务就是持久的。典型配置是三个安全保管者;提交确认需要三个中的两个。 - 页面服务器是最新颖的部分。它不直接存储页面。它存储分层的 WAL——基础镜像加增量——并且在收到计算节点的页面请求时,它会按请求的 LSN 物化请求的页面并将其发送回去。层文件会溢出到对象存储作为冷存储层。热页面保留在页面服务器的本地磁盘上。
您从中得到什么:
- 计算是无状态的。计算节点没有有价值的持久本地状态。您可以终止它、重启它、将其缩减到零、从零再扩展回来,或在不同主机上运行它,只要它能与安全保管者和页面服务器通信,它就能从中断处继续。这是“扩展到零”以及 Neon 闻名的亚秒级分支的架构基础。
- 存储是无限的。对象存储是持久的基础设施;您不需要预先配置磁盘大小。定价基于使用量而非分配量。
- 分支是写时复制的。由于页面是根据 WAL 按需重建的,因此在 LSN N 处创建一个分支是一个元数据操作。该分支开始时读取与其父分支相同的层文件,并且仅当写入新的 WAL 时才会产生差异。
- 复制语义是 Neon 特定的。没有
archive_command,没有来自计算节点的pg_basebackup,没有传统意义上的流复制。复制原语是不同的。它们存在,但不是 PostgreSQL 管理手册中的那些。
什么保留了下来,什么没有:
- MVCC 未变。可见性、快照、锁定、死锁检测、vacuum——都是上游 Postgres 的行为。
- 扩展兼容性很高,但并非完全兼容。任何在缓冲区管理器之上完成工作的扩展都能正常工作。任何探查 WAL 流、假设本地文件存在、或依赖后台工作进程写入磁盘路径的扩展都需要检查。您实际使用的大多数生产扩展——pgvector、pg_stat_statements、pgaudit、PostGIS、pg_partman——无需修改即可工作。少数专门的扩展则不行。
- Vacuum 仍然重要。这是大多数人会弄错的一点。即使存储是无限的,并且旧的层文件会在页面服务器上进行垃圾回收,PG 堆文件中的死元组仍然会消耗缓冲区空间并影响查询计划。
VACUUM和 autovacuum 仍然需要运行,并且它们的调优仍然是一个实际问题。它产生的 I/O 形态不同(读取来自网络页面服务器而非本地 SSD),但它产生的工作负载是相同的。 - fsync 语义就是安全保管者的往返时间。提交的延迟下限是到安全保管者仲裁的网络延迟加上安全保管者本地的
fsync。在单可用区部署中,这很快。在有意的多可用区仲裁部署中,则不然,并且您的commit_delay/ 批处理策略比在本地磁盘上更重要。 - pg_wal 不存在了。或者说,它从不在本地磁盘写入任何东西。那些抓取 WAL 目录、以旧方式监控归档延迟或依赖针对计算节点的
pg_basebackup的工具需要不同的管道。
在 Databricks Lakebase 中,这个引擎位于 Unity Catalog 和 Databricks 数据湖仓旁边。其集成方式是,Lakebase 中的操作型数据可以通过 Delta Sharing 或 Unity Catalog 联合暴露给数据湖仓,但 Lakebase 存储本身既不是 Iceberg 也不是 Delta。计算节点从 Neon 页面服务器读取数据,而不是直接从对象存储读取,磁盘上的格式是内部的。如果您想直接用 Spark 或 Trino 查询 Lakebase 数据,需要通过 Unity Catalog 联合进行,而不是让那些引擎直接指向底层对象存储。
各自在何处不再是 PostgreSQL
这是营销资料回答得最不 helpful 的问题。
pg_lake 在外部数据包装器边界处不再是 PostgreSQL。堆表是真正的 PG;Iceberg 表则不是,它们具有 Iceberg 的语义,而非 PG 的语义。跨越堆/Iceberg 边界的跨表事务是尽力而为的,并非严格原子性的。DuckDB sidecar 的行为更接近于“另一个与 Postgres 兼容的数据库”,而不是“Postgres 内部更快的执行路径”——当规划器的选择在 PG 和pgduck_server之间交叉时,您正在跨越一个进程边界。这些都不是致命的问题。但都是真实存在的问题。
Lakebase 在存储管理器处不再是 PostgreSQL。SQL 是相同的,MVCC 模型是相同的,但缓冲区管理器以下的所有代码都不同。为上游 PostgreSQL 编写的操作手册——物理复制调优、基于pg_basebackup的灾难恢复、传统的pg_wal/archive_command/restore_command流程、底层文件系统级备份——都不适用了。存在替代方案,但它们是 Neon 的,而不是 Postgres 的。依赖存储管理器确切行为的扩展需要验证。
对于在职的 DBA 来说,实用的心智模型是:
- 当您的瓶颈是“我需要对存在于数据湖仓中的数据进行分析访问,同时完全保留 PostgreSQL 的能力”时,选择pg_lake。您保留操作型 PostgreSQL 的所有部分,包括您已经拥有的每一个运维工具,并获得一条通往 Iceberg 的清晰路径。
- 当您的瓶颈是“我想要一个托管型、无服务器、可缩放到零、毫秒级分支、并与 Unity Catalog 集成的 OLTP Postgres”时,选择Lakebase。您保留 SQL 接口,但放弃了对存储堆栈的直接所有权,并接受运维工具包不再是您一直使用的那套。
这些是不同的赌注。在足够高的抽象层次上,称它们为“PostgreSQL + 数据湖仓”都是正确的,但在每一个更低的层次上,这种说法都是误导性的。请分别为它们建立心智模型。如果您正在进行架构评审,而其中一个或两个都在讨论之列,最糟糕的做法就是仅仅因为它们在营销中都使用了“Postgres”这个词,就将它们视为可以互换的。