DuckLake DataFrame:用 Pandas/Polars/PySpark 读写湖格式数据
DuckLake 的规范如此简单,连"机器人"(clanker)都能用它构建 DataFrame 读写器。

什么是 DuckLake?
DuckLake v1.0 是 DuckDB 团队推出的开放湖格式规范。它的核心理念可以用一句话概括:DuckLake 就是一个 Parquet 文件集合加上一个元数据目录。
与传统的数据湖格式(如 Apache Iceberg、Delta Lake)相比,DuckLake 的设计哲学是极致简化:
- 写入时:你告诉 DuckLake 你产生了哪些 Parquet 文件
- 读取时:DuckLake 告诉你应该读取哪些 Parquet 文件
- 元数据管理:可选的后端,支持 DuckDB、SQLite、PostgreSQL 等
这种设计使得 DuckLake 的实现复杂度远低于 Iceberg 或 Delta Lake。正如 DuckDB 团队所说:“所有的重活都由经过验证的系统完成——要么是 Parquet 读写器(如 PyArrow),要么是数据库管理系统目录(如 PostgreSQL)。”
项目背景:用 AI 构建 DuckLake 实现
2026 年 5 月,DuckDB 团队发起了一项有趣的实验:用 AI 辅助从零构建一个 DuckLake 的 DataFrame 实现。
项目负责人 Pedro Holanda 设定了一个简单的目标——用 Python 完全实现一个支持 Pandas、Polars 和 PySpark 的 DuckLake 读写器。而执行这项任务的"工程师"是 Dr. Peter van Holland(Peter van Holland 博士),他是一位以极速开发闻名的数据库工程师。
结果令人震惊:在六天内,Dr. Van Holland 完成了读写功能的全部实现,并发布了 ducklake-dataframe 库。 这个库实现了与 DuckLake v1.0 规范的完全兼容,支持 Pandas、Polars、PySpark 三大 DataFrame 库,以及 DuckDB、SQLite、PostgreSQL 三种目录后端。
核心架构
ducklake-dataframe 的核心架构非常简单:
┌─────────────────────────────────────────────────┐
│ DataFrame Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Pandas │ │ Polars │ │ PySpark │ │
│ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
│ └──────────────┼───────────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ ducklake-dataframe │ │
│ │ (Parquet I/O) │ │
│ └────────┬────────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Catalog Backend │ │
│ ┌──────┤ DuckDB/SQLite/PG ├──┐ │
│ │ └─────────────────────┘ │ │
│ │ │ │
│ ┌─▼──────────┐ ┌─────▼─────────┐ │
│ │ Parquet │ │ Parquet Files │ │
│ │ (PyArrow) │ │ (S3/GCS/本地) │ │
│ └────────────┘ └───────────────┘ │
└─────────────────────────────────────────────────┘
关键点在于:DuckLake 是一个规范(spec),而非某个特定工具的实现。 这意味着:
- 运行时不需要 DuckDB 作为必需依赖
- 目录后端可以是任意关系型数据库
- Parquet 读写可以使用任何成熟的库(PyArrow 是最常见的选择)
快速上手
安装
pip install ducklake-dataframe pandas pyarrow
创建 DuckLake 目录
首先,使用 DuckDB 的 ducklake 扩展创建一个 DuckLake 目录:
import duckdb
# 连接并创建 DuckLake
conn = duckdb.connect()
conn.execute("""
ATTACH 'ducklake:sqlite:catalog.ducklake' AS lake
(DATA_PATH 'data/');
""")
# 创建表
conn.execute("""
CREATE TABLE lake.users (
id INTEGER,
name VARCHAR,
email VARCHAR,
score DOUBLE,
active BOOLEAN
);
""")
# 插入数据
conn.execute("""
INSERT INTO lake.users VALUES
(1, 'Alice', '[email protected]', 95.5, true),
(2, 'Bob', '[email protected]', 87.3, true),
(3, 'Carol', '[email protected]', 72.1, false),
(4, 'Dave', '[email protected]', 91.0, true),
(5, 'Eve', '[email protected]', 88.8, true);
""")
conn.close()
用 Pandas 读取 DuckLake 数据
现在,不依赖 DuckDB 运行时,直接用 ducklake-dataframe 读取:
from ducklake_dataframe.pandas import DuckLakePandas
# 连接到已有的 DuckLake
dl = DuckLakePandas("ducklake:sqlite:catalog.ducklake", data_path="data/")
# 像读取普通表一样读取
df = dl.read("users")
print(df)
输出:
id name email score active
0 1 Alice [email protected] 95.5 True
1 2 Bob [email protected] 87.3 True
2 3 Carol [email protected] 72.1 False
3 4 Dave [email protected] 91.0 True
4 5 Eve [email protected] 88.8 True
用 Pandas 写入 DuckLake
import pandas as pd
# 创建新的 Pandas DataFrame
new_data = pd.DataFrame({
'id': [6, 7, 8],
'name': ['Frank', 'Grace', 'Henry'],
'email': ['[email protected]', '[email protected]', '[email protected]'],
'score': [93.2, 85.7, 79.4],
'active': [True, True, False]
})
# 追加写入 DuckLake
dl.append("users", new_data)
# 重新读取验证
df_updated = dl.read("users")
print(len(df_updated)) # 输出 8
跨引擎互操作
这才是真正强大的地方——不同 DataFrame 引擎之间可以无缝读写同一个 DuckLake:
# 用 Polars 写入
import polars as pl
from ducklake_dataframe.polars import DuckLakePolars
dl_polars = DuckLakePolars("ducklake:sqlite:catalog.ducklake", data_path="data/")
polars_df = pl.DataFrame({
'id': [9, 10],
'name': ['Ivy', 'Jack'],
'email': ['[email protected]', '[email protected]'],
'score': [96.1, 82.3],
'active': [True, True]
})
dl_polars.append("users", polars_df)
# 再用 Pandas 读取 Polars 写入的数据
dl_pandas = DuckLakePandas("ducklake:sqlite:catalog.ducklake", data_path="data/")
df_all = dl_pandas.read("users")
print(len(df_all)) # 输出 10!
与传统数据湖方案的对比
| 特性 | DuckLake | Apache Iceberg | Delta Lake |
|---|---|---|---|
| 规范复杂度 | 极简(Parquet + 目录) | 复杂(事务日志 + 快照) | 中等(JSON 日志) |
| 实现难度 | 低(数天可完成) | 高(需数月) | 中高 |
| 目录后端 | 任意 RDBMS | 专用 Catalog 服务 | 任意文件系统 |
| 运行时依赖 | 可选 | 必须 | 必须 |
| 跨语言支持 | 按需实现 | 广泛 | 广泛 |
| ACID 事务 | 基础 | 完整 | 完整 |
| 时间旅行 | 基础 | 完整 | 完整 |
| Schema 演进 | 基础 | 完整 | 完整 |
| 适用场景 | 轻量级数据湖 | 企业级数据湖 | 企业级数据湖 |
技术亮点
1. 读写对等性(Read-Write Parity)
Dr. Van Holland 设定的核心目标是:无论 DuckDB 写入什么,他都应该能读取;无论他写入什么,DuckDB 也应该能读取。 这个目标完全达成了。
2. 极低的依赖开销
ducklake-dataframe 的核心依赖只有:
- DataFrame 库(Pandas / Polars / PySpark)
- PyArrow(用于 Parquet 读写)
- 目录后端(SQLite / PostgreSQL / DuckDB,均为可选)
没有复杂的运行时组件,没有 JVM 依赖,没有外部服务。
3. 与 DuckDB 生态的无缝集成
虽然 ducklake-dataframe 可以不依赖 DuckDB 运行,但它与 DuckDB 生态完全兼容:
# 用 DuckDB 查询 DuckLake 中的数据
import duckdb
conn = duckdb.connect()
conn.execute("ATTACH 'ducklake:sqlite:catalog.ducklake' AS lake")
result = conn.execute("SELECT * FROM lake.users WHERE score > 90").fetchdf()
print(result)
4. 数据内联(Data Inlining)支持
DuckLake 支持将小数据直接存储在 Parquet 文件内部,而不是单独的文件。这在处理小表或频繁更新的维度表时特别有用。
变现建议
对于希望将 DuckLake 技术转化为商业价值的团队,以下是几个方向:
1. 数据湖即服务(DLaaS)
基于 ducklake-dataframe 构建轻量级的数据湖托管服务:
- 目标客户:中小型企业、初创公司
- 核心价值:无需管理复杂的 Iceberg 集群,开箱即用的数据湖
- 定价参考:$0.02/GB/月存储 + $0.001/GB 查询费用
2. 多引擎数据集成平台
利用 DuckLake 的跨引擎特性,构建统一的数据交换平台:
- 场景:团队中同时使用 Pandas、Polars、PySpark
- 价值:消除引擎间的数据格式转换成本
- 商业模式:SaaS 订阅 $99-499/月
3. 数据湖性能优化咨询
基于 DuckLake 的简单架构,为企业提供数据湖性能调优服务:
- 服务内容:Parquet 文件布局优化、分区策略设计、查询性能分析
- 收费模式:项目制 $5,000-20,000/项目
4. 嵌入式数据分析产品
将 ducklake-dataframe 嵌入到你的 SaaS 产品中:
- 优势:零运维成本,客户端即可读写结构化数据湖
- 适用场景:BI 工具、数据科学平台、自动化报告系统
总结
DuckLake 的核心理念是:数据湖不应该那么复杂。 通过将复杂性交给经过验证的 Parquet 格式和现有的关系型数据库目录,DuckLake 提供了一个极简但功能完备的数据湖实现方案。
ducklake-dataframe 项目的最大意义不在于它本身有多完善(作者自己也承认它不适合生产环境),而在于它证明了 DuckLake 规范的简洁性——一个简单的规范,配合成熟的开源库,就能在极短时间内构建出一个功能完整的湖格式实现。
随着 DuckLake v1.0 的正式发布和更多生态工具的出现,我们有理由相信,数据湖的未来可能会比我们想象的更加简单和高效。
📚 延伸阅读: