湖仓引擎系列——第8阶段:存储与访问
内容提要
本文是湖仓引擎系列最后一篇,介绍存储抽象与访问层:定义Backend接口(Put/Get/Delete/Exists),实现本地文件系统与S3两种后端,并通过构造函数注入使引擎不依赖具体存储;元数据位置与数据位置相互独立,可分别存放于Postgres和S3,便于DuckDB、Spark、pandas等多引擎共享同一目录;提供CLI REPL,支持建表、插入、扫描、删除、时间旅行等命令;开放表格式由标准Parquet、SQL元数据和约定三部分组成。
延伸解读
存储抽象让引擎与介质解耦
文章用仅含 Put/Get/Delete/Exists 四个方法的 Backend 接口覆盖湖引擎全部存储操作,本地文件系统与 S3 各自实现即可互换。Lake 结构体通过构造函数注入后端,测试用临时目录、生产用真实凭证,引擎本身不感知具体存储。这种设计意味着新增 GCS、Azure Blob 等后端只需再实现四个方法,无需改动上层逻辑,是理解湖仓可移植性的关键切入点。
元数据与数据位置分离的实际意义
文章强调目录的元数据位置与数据位置相互独立,例如元数据放 Postgres、数据放 S3。由此 DuckDB、Spark、pandas 可分别通过各自方式连接同一元数据库并读取同一批 Parquet 文件,看到相同的表、快照与数据。这一分离正是计算与存储解耦的落点,也是多引擎共享同一份湖上数据的前提,而非单纯的部署细节。
开放表格式的三块拼图与 Iceberg 差异
文章把开放表格式拆为标准 Parquet 数据文件、标准 SQL 元数据库和文件组织约定三部分,任何同时理解三者的系统都能参与。与 Iceberg 用 Avro/JSON 存元数据、客户端需自行实现清单解析和快照解析不同,DuckLake 把这类工作交给 SQL 数据库,客户端只需查元数据拿文件列表再读 Parquet。这降低了接入门槛,但也意味着对元数据库的依赖。
外部访问示例的边界要看清
文章给出的 pandas 示例需手动按 begin_snapshot 与 end_snapshot 过滤快照,且明确说明不处理删除文件,仅用于演示格式的开放性。因此这类外部读取适合验证互通性,不能等同于完整查询能力;涉及删除或更新的表,直接用 pandas 拼接 Parquet 可能得到与引擎不一致的结果,读者应留意这一限制。
Q&A
湖仓引擎的存储抽象层是如何设计的?
通过定义 Backend 接口,包含 Put、Get、Delete、Exists 四个方法,任何实现了这些方法的结构体都可作为存储后端。引擎通过构造函数注入 Backend,因此不依赖具体存储实现,可灵活切换本地文件系统或 S3。
为什么元数据位置和数据位置要分开存放?
元数据可以存放在 Postgres 等数据库中,而数据文件存放在 S3 等对象存储上。这种分离使得多个计算引擎(如 DuckDB、Spark、pandas)可以指向同一个目录,共享相同的表、快照和数据。
本地文件系统后端和 S3 后端在实现上有什么主要区别?
本地后端将所有路径相对于 basePath 拼接,直接使用 os 包操作文件;S3 后端则通过 aws-sdk-go-v2 客户端,将配置的 prefix 与相对路径拼接成对象键,调用 PutObject、GetObject 等 API 进行读写。
CLI REPL 支持哪些命令来操作湖仓?
支持 create-schema、create-table、insert、scan(可带 at <snapshot_id> 进行时间旅行)、delete、add-column、drop-column、rename-column 以及 snapshots 等命令。
外部引擎如何访问 DuckLake 的数据?
因为数据以标准 Parquet 文件存储,元数据在标准 SQL 数据库中,任何工具都可以访问。例如 DuckDB 可通过 ATTACH 语句直接查询,pandas 可通过 psycopg2 查询元数据表获取文件列表,再用 pd.read_parquet 读取数据文件。
DuckLake 的开放表格式由哪几部分组成?与 Iceberg 有何不同?
由标准 Parquet 数据文件、标准 SQL 数据库中的元数据以及文件组织与元数据结构的约定三部分组成。与 Iceberg 不同,Iceberg 的元数据是 Avro/JSON 文件需要自定义解析,而 DuckLake 的元数据在数据库中,客户端只需执行 SQL 查询即可获取文件列表,简化了实现。