OpenDuck 开源, DuckDB 开搞云边协同
一句话判断
OpenDuck 的价值不在于“又做了一个 DuckDB 远程查询代理”,而在于它把 MotherDuck 已经验证过的三件事拆成了可自托管、可替换后端的开源形态:差分存储、混合执行、DuckDB 原生 ATTACH 体验。
我的观点:如果你的团队已经大量使用 DuckDB,并且卡在“本地分析很顺手,但共享、并发、远程计算和云端数据访问很难工程化”的阶段,OpenDuck 值得作为架构原型和内部平台方向评估。
前提:你接受它目前仍是早期项目;你愿意从源码构建 DuckDB 扩展;你的团队有 Rust、C++、gRPC、Arrow、Postgres、对象存储的工程能力;你的核心诉求是 DuckDB 云化/协同化,而不是找一个即开即用的商业数仓。
背景:DuckDB 云化的关键不是“放到服务器上跑”
DuckDB 的优势是嵌入式、单进程、列式分析、文件即数据库。这个模型对个人分析和应用内分析非常友好,但进入团队协作后,矛盾会集中出现:
.duckdb 文件 | ||
MotherDuck 的 CIDR 2024 论文和 DuckDB 官方 library 页面把这个方向称为 hybrid query processing:查询可以部分在本地、部分在云端执行。MotherDuck 的 differential storage 博文进一步说明,DuckDB 原生文件要进入云端协作场景,需要把逻辑数据库文件映射到可追加、可快照、可共享的存储层。
OpenDuck 的定位就是把这些思想开源化,但它并不声称和 MotherDuck wire-compatible。README 明确说它受 MotherDuck 启发,重新实现了类似架构思想,并提供开放协议、开放后端和开放扩展。
场景:谁应该关心 OpenDuck
最适合先关注 OpenDuck 的角色不是普通 BI 用户,而是这几类工程团队:
ATTACH 'openduck:...' 提供远程数据库体验 | ||
ExecutionService 即可兼容数据面 |
我的观点:OpenDuck 更像“DuckDB 云化的可运行架构蓝图”,不是成熟的即插即用产品。
前提:你愿意用早期项目换取架构透明性和可改造性。
支撑证据:仓库 layout 显示它同时包含 exec-gateway、exec-worker、exec-proto、diff-*、extensions/openduck、clients/python、examples;协议只暴露少量 gRPC RPC;扩展 README 说明 remote table 通过 DuckDB catalog 进入优化器和执行引擎。
如果前提崩塌:如果团队目标是“下周上线给业务使用”,应该把 OpenDuck 放在实验/PoC,而不是生产依赖。
传统方案为什么不够
方案一:继续传 .duckdb 文件
简单,但协作一旦上来就会变成文件分发问题。谁拥有最新版本?谁负责写入?如何回滚?如何避免多个副本之间的业务口径漂移?这种方式适合单人分析和离线交付,不适合持续协作。
方案二:把 DuckDB 放在远程服务里,所有 SQL 都发过去
这能解决集中访问,但丢掉了 DuckDB 最强的本地计算优势。用户本地已有 CSV、Parquet、临时表、缓存数据时,如果所有查询都远程执行,就要先上传或落地。对交互式分析来说,这会增加等待、复制和安全暴露面。
方案三:使用通用 SQL-over-Arrow 协议
Arrow Flight SQL 是 Apache Arrow 官方协议,用 Arrow 内存格式和 Flight RPC 访问 SQL 数据库,覆盖 metadata、query execution、prepared statement 等通用数据库能力。它适合做跨数据库、高性能、标准化的数据访问层。
但 OpenDuck 的批判点是:通用协议通常是“客户端驱动访问服务端数据库”,而不是把远程表变成 DuckDB 原生 catalog entry,更不会让 DuckDB 优化器天然理解本地表和远程表之间的执行位置。
方案四:只用 DuckLake
DuckLake 是 DuckDB 生态里面向 lakehouse catalog 的方案。DuckDB 官方文档说明 ducklake 扩展可 ATTACH DuckLake 格式数据库,典型用法是 metadata 加 data path;README 也承认 OpenDuck 不替代 DuckLake,两者层次不同:DuckLake 管表、Parquet 文件和事务元数据,OpenDuck 管 DuckDB 文件 I/O、差分层、快照和远程执行传输。
因此,DuckLake 更适合“表已经是对象存储里的 Parquet 数据集”;OpenDuck 更适合“我仍需要 DuckDB-native storage、索引、全文检索或远程 DuckDB 执行能力”。
OpenDuck 到底做了什么
OpenDuck 提供四个核心构件:
DuckDB C++ 扩展:注册 openduck:和od:storage scheme,让用户通过ATTACH把远程数据库挂进 DuckDB。开放协议: proto/openduck/v1/execution.proto定义ExecuteFragment、CancelExecution、RegisterWorker、Heartbeat四个 RPC,数据以 Arrow IPC batch 流式返回。Gateway/Worker 执行层:gateway 做 token auth、worker registry、affinity routing、plan splitting、backpressure;worker 嵌入 DuckDB 执行片段并流式返回结果。 Differential storage:用 append-only layer、snapshot、Postgres metadata、对象存储 sealed layer 来把 DuckDB 看到的随机访问文件转成云友好的层化存储。
架构原则:把“远程”藏进 DuckDB 原生接口
扩展 README 给出了关键路径:
ATTACH 'openduck:mydb?token=...' AS cloud
-> 创建 OpenDuckCatalog
SELECT * FROM cloud.users
-> DuckDB 解析 cloud.main.users
-> schema entry 通过 gRPC 探测远程 schema
-> table entry 返回 scan function
-> scan function 拉取 Arrow IPC 并转换为 DuckDB DataChunk
这条路径的意义是:用户不是调用一个外部 driver,也不是写 remote_query(...) 包裹函数,而是让远程表成为 DuckDB catalog 里的一级对象。这样 JOIN、CTE、subquery、类型系统和优化器才有机会把它当成普通表处理。
源码侧可以看到对应边界:
extensions/openduck | src 目录 | |
proto/openduck/v1/execution.proto | ||
crates/exec-gateway | main.rs0.0.0.0:7878 | |
crates/exec-worker | OPENDUCK_WORKER_DB、DuckLake metadata/data env | |
crates/diff-core | StorageBackend | |
crates/diff-metadata | ||
crates/diff-blob | ||
crates/diff-fuse |
数据流:一次混合查询如何走
以 local.products JOIN cloud.sales 为例,OpenDuck README 展示的计划形态是:本地扫描本地表,远程扫描远程表,在边界插入 bridge operator,只传中间结果。真正的收益不在“远程能查”,而在“避免把所有远程原始数据拉回本地,或把所有本地临时数据上传远端”。
我的推断是:OpenDuck 真正有价值的执行模式,是远程过滤、聚合、扫描尽量靠近云端数据,本地小表、临时表、交互式计算留在本地。
前提:plan splitting 足够准确,bridge operator 的中间结果远小于原始远程数据,网络延迟和序列化成本不吞掉收益。
如果前提不成立:简单 remote query、Arrow Flight SQL、直接 DuckDB 读 Parquet,甚至把计算完全下推到云数仓,都会更稳。
存储模型:差分层解决的是 DuckDB 文件云化问题
OpenDuck 的 diff-core 把存储抽象成一个逻辑随机访问文件,暴露 read、write、flush、fsync、seal、truncate。README 把它解释为:DuckDB 看到普通文件;OpenDuck 持久化为不可变 sealed layers;snapshot 提供一致性读取;一个序列化写路径,多个并发读者。
MotherDuck 的 differential storage 博文提供了这个架构思想的背景:把数据库表示成有序 layer,每个 layer 对应某个 checkpoint 之后的差异,snapshot layer metadata 保存在单独的 OLTP 数据库中。OpenDuck README 对应采用 Postgres metadata 和 object store sealed layers。
我的观点:差分存储是 OpenDuck 区别于“远程 SQL 代理”的根部能力。
前提:你确实需要 DuckDB-native file semantics 的远程共享、快照、一致性读取,而不是只管理 Parquet 表。
支撑证据:README 明确把 differential storage 放在首位;diff-core、diff-layer-fs、diff-blob、diff-metadata、diff-fuse 组成独立模块;execution.proto 的 ExecuteFragmentRequest 包含可选 snapshot_id。
如果前提崩塌:如果你的数据天然是 Parquet/Delta/Iceberg 表,且主要问题是 catalog 和事务元数据,DuckLake、Iceberg、Delta Lake 类方案更直接。
前后效果对比
目前没有发现 OpenDuck 官方发布的性能 benchmark 或生产 case study,因此下面只做机制层面的比较,不声明性能提升倍数。
ATTACH 'openduck:mydb' AS cloud | |||
execution.proto | |||
.duckdb | |||
INSTALL | |||
竞品与替代方案
使用场景
场景一:团队共享 DuckDB-native 数据库
症状:分析师和开发者反复传 .duckdb 文件,版本和口径不可控。
为什么 OpenDuck 有帮助:它把远程数据库挂载成 DuckDB catalog,读者可通过相同 SQL 访问远程表。
示例:
import duckdb
con = duckdb.connect(config={"allow_unsigned_extensions": "true"})
con.execute("LOAD 'extensions/openduck/build/release/extension/openduck/openduck.duckdb_extension';")
con.execute("ATTACH 'openduck:mydb?endpoint=http://localhost:7878&token=your-token' AS cloud;")
con.sql("SELECT * FROM cloud.users LIMIT 10").show()
注意:当前写路径、并发写入、权限模型需要结合源码和测试自行验证,不能直接假设等价于成熟云数据库。
场景二:本地临时表 JOIN 云端大表
症状:本地有小维表或临时分析结果,云端有大事实表,全量搬运很慢。
为什么 OpenDuck 有帮助:计划可以拆成本地和远程片段,只跨网络传中间结果。
示例:
SELECT p.name, s.revenue
FROM local_products p
JOIN cloud.sales s ON p.id = s.product_id;
注意:只有当远程过滤/聚合后中间结果足够小,混合执行才可能带来收益。
场景三:内部实现一个自定义后端
症状:你已有数据服务,但希望 DuckDB 用户通过统一 ATTACH 使用。
为什么 OpenDuck 有帮助:协议数据面足够小,实现 ExecuteFragment 和 CancelExecution 并返回 Arrow IPC batch,就有机会成为兼容后端。
注意:如果你需要标准 metadata、prepared statement、跨语言生态,Arrow Flight SQL 的协议面更成熟。
场景四:研究 DuckDB 文件云化
症状:想把 .duckdb 的随机读写文件语义映射到对象存储。
为什么 OpenDuck 有帮助:StorageBackend、Postgres metadata、object store layer、FUSE adapter 提供了可读源码路径。
注意:这更像系统软件工程,不是简单配置项。
最佳实践
先把它当 PoC,不要直接放生产关键路径。当前无 release/tag,扩展未发布到 DuckDB 官方扩展仓库。 把 token 当最低限度鉴权,不要把它理解为完整权限系统。需要 TLS、网络隔离、密钥轮换、审计和最小权限。 先验证查询计划和中间结果体积,再谈性能收益。混合执行怕的是 bridge 边界传输过大。 把 Postgres metadata 和对象存储纳入备份/恢复设计。差分层一旦元数据和对象层不一致,恢复会比单文件复杂。 版本固定。DuckDB 扩展存在二进制兼容性约束,DuckDB 官方文档说明扩展通常和 DuckDB 版本、平台绑定。 观测先行。gateway、worker、backpressure、cancel、heartbeat 都要有日志、指标和报警。 保留回退路径。PoC 阶段至少保留本地 DuckDB 文件、DuckLake/Parquet 或原有数仓查询路径。
动手步骤
以下命令来自 OpenDuck README 和 extension README,路径按仓库根目录执行。
1. 构建后端
cargo build --workspace
2. 构建 DuckDB 扩展
macOS 需要准备依赖:
brew install protobuf grpc apache-arrow
其他平台可按 extension README 使用 vcpkg。然后构建:
cd extensions/openduck
make
产物路径:
extensions/openduck/build/release/extension/openduck/openduck.duckdb_extension
3. 启动服务
export OPENDUCK_TOKEN=your-token
cargo run -p openduck -- -d mydb --token your-token
README 里 gateway 默认 endpoint 示例是 http://localhost:7878;crates/exec-gateway/src/main.rs 也显示默认监听 0.0.0.0:7878。
4. Python 连接
import duckdb
con = duckdb.connect(config={"allow_unsigned_extensions": "true"})
con.execute("LOAD 'extensions/openduck/build/release/extension/openduck/openduck.duckdb_extension';")
con.execute("ATTACH 'openduck:mydb?endpoint=http://localhost:7878&token=your-token' AS cloud;")
con.sql("SELECT 1 AS x").show()
con.sql("SELECT * FROM cloud.users LIMIT 10").show()
5. Python wrapper
pip install -e clients/python
export OPENDUCK_TOKEN=your-token
import openduck
con = openduck.connect("mydb")
con.sql("SELECT 1 AS x").show()
6. CLI
duckdb -unsigned -c "
LOAD 'extensions/openduck/build/release/extension/openduck/openduck.duckdb_extension';
ATTACH 'openduck:mydb?token=your-token' AS cloud;
SELECT * FROM cloud.users LIMIT 10;
"
结论
OpenDuck 最值得看的地方,是它把 DuckDB 云化拆成了三个明确接口:
如果你已经在 DuckDB 上构建内部数据工具,OpenDuck 给你的不是一个“今天就替换生产”的答案,而是一条值得验证的路线:保留 DuckDB 的本地体验,同时把存储、执行和协作逐步拉到可服务化、可自托管的形态。
如果你要稳定产品,今天应谨慎;如果你要抢先理解 DuckDB 云化的工程边界,今天正适合读源码、跑 PoC、测 workload、参与贡献。