第 12 篇 把 Trino 的 Query → Stage → Task → Page 流拆到底:Coordinator 做分布式 planning,Worker 之间用 Exchange 传 Page。很多读者会问:笔记本上 DuckDB 一条 SQL 也能 join 百万行、读 Parquet/Iceberg——它和 Trino 差在哪?Rust 写的 DataFusion 又扮演什么角色?
本文是系列 第 14 篇,回答 嵌入式 OLAP 与 分布式 MPP 的分工:
- DuckDB:无 server、进程内 的向量化分析引擎,适合单机/CLI/应用内嵌读 Parquet 与 Iceberg。
- DataFusion:可嵌入的 Rust 查询库,Arrow 原生 plan,被自研引擎与加速层(如 Comet)当作执行底座。
- 与 columnar-engine 第 11 篇 DuckDB 架构 的分工:那边讲 Row Group / Column Segment 存储;本篇讲 无 server 的 SQL 规划与执行,以及和 Trino 读湖路径的对照边界。
实测环境:本机 WSL2(Linux 6.6.87.2)、i9-12900K / 32 GiB。DuckDB 1.5.4(Python 绑定 + CLI)为本机真实执行;未部署 Trino/Spark(无 JVM 查询集群)。Trino 行为按官方文档引用,不粘贴未实测的
EXPLAIN ANALYZE数字。复现脚本:reproduce/run_duckdb_experiments.py。
一、嵌入式 vs 分布式:分界不在「能不能读 Parquet」
第 1 篇 已给出分界表,本篇从 执行拓扑 收紧结论:
| 维度 | DuckDB / DataFusion(嵌入式) | Trino / Spark(分布式) |
|---|---|---|
| 进程模型 | 嵌入宿主或 CLI 单进程 | Coordinator/Driver + 多 Worker/Executor |
| 并行 | 多线程 pipeline,共享地址空间 | 跨节点 Task + 网络 shuffle |
| 联邦 | ATTACH / 扩展读外部
catalog,规模受单机内存与线程限制 |
Connector SPI,跨源 join 是设计目标 |
| 并发租户 | 单进程内串行或有限并行,无远程 SQL 服务语义 | 多 session、resource group、队列 |
| 读湖 planning | 单进程内 metadata + scan(lakehouse/18 实验二) | Coordinator 集中 planning,split 分发 |
工程判断:数据与并发能放进 一台机器、一个进程 的内存与磁盘预算 → DuckDB 往往足够;需要 多用户交互式联邦、PB 级数据并行扫、跨机 shuffle → 上 Trino/Spark(第 18 篇 决策树)。
flowchart LR
subgraph emb ["Embedded"]
APP["Notebook / Service"]
DDB["DuckDB / DataFusion"]
FS["Parquet / Iceberg files"]
APP --> DDB --> FS
end
subgraph mpp ["Distributed MPP"]
CLI["JDBC / CLI"]
COORD["Coordinator"]
W1["Worker"]
W2["Worker"]
LAKE["Object storage"]
CLI --> COORD --> W1
COORD --> W2
W1 --> LAKE
W2 --> LAKE
end
二、DuckDB 查询路径:Binder → Optimizer → Parallel Pipeline
DuckDB 官方 Internals(A 级)把路径概括为:Parser → Binder → Logical Optimizer → Physical Plan → Pipelines。与 Trino 的 Analyzed → Logical → Distributed plan 同构,但 没有 Distributed 层——所有算子在同一进程内调度。
2.1 与 columnar-engine/11 的分工
| 话题 | columnar-engine/11 | 本篇 |
|---|---|---|
| 持久化 | Row Group、Column Segment、checkpoint | 不展开 |
| 外部文件 | read_parquet 一句带过 |
scan 下推、投影裁剪、读湖扩展 |
| 对比对象 | ClickHouse MergeTree | Trino Page 流、DataFusion |
2.2 向量化与 morsel-driven
第 7 篇 讲过三种执行语义;DuckDB 是 向量化 batch + morsel-driven 并行 的典型:表按 Row Group(默认约 122880 行)切分,每个 pipeline 线程处理一个 morsel,work-stealing 平衡负载。Trino Worker 上是 Page 流(列式 block),跨 Worker 才走 Exchange——这是分布式多出来的一层。
2.3 读湖:iceberg / delta 扩展
DuckDB 文档(Lakehouse formats,A 级)说明:
iceberg扩展:原生实现,不依赖 Java Iceberg 库;iceberg_scan('.../metadata.json')只读,REST catalog 可ATTACH后写入。delta扩展:基于 delta-kernel-rs,读 + 有限 blind insert。
读路径复用 Parquet scan 的 data skipping(row group/file)、projection pushdown(与 lakehouse/18 ③④ 层衔接)。Planning 全在单进程内完成,没有 Trino 的 split 调度与 dynamic filter 跨节点等待(Trino 481 docs, Dynamic filtering)。
三、Apache DataFusion:库,不是产品
DataFusion(Apache 项目,A 级:Concepts / physical optimizer)是 Rust 写的可嵌入查询引擎:
| 组件 | 职责 |
|---|---|
| SQL parser / planner | 可选 SQL 前端,或接收外部 LogicalPlan |
| Physical optimizer | 规则链:谓词下推、投影裁剪、join 重排等 |
| Execution | Arrow RecordBatch 上的向量化算子 |
| TableProvider | 扩展读
Parquet、Iceberg(iceberg-rust)、Delta(delta-rs) |
DataFusion 不提供 Trino 式长期运行的 SQL server(社区有 Ballista 等分布式实验,生产主流仍是嵌入)。典型用途:在 Rust 服务内嵌 SQL、作为 Comet 等 Spark 向量化加速的执行层、原型验证 Substrait 计划。
与 DuckDB 对照:
| 维度 | DuckDB | DataFusion |
|---|---|---|
| 交付形态 | 独立引擎 + 多语言绑定 | Rust crate,嵌入宿主 |
| 存储 | 内置 DB 文件 + 外部 Parquet | 无默认存储,靠 TableProvider |
| 优化器 | 内置 C++ optimizer | Physical rule 可插拔 |
| 读 Iceberg | 官方 iceberg 扩展 |
社区 iceberg-rust,版本需核对 spec |
选型 DuckDB:要开箱即用的 CLI、Python/R 分析、单机读湖。DataFusion:要把查询能力嵌进 自研 Rust 数据平面,或对接 Arrow 生态。
四、本机实验:计划形状与 Parquet 投影下推
4.1 小规模 join 的物理计划
reproduce/run_duckdb_experiments.py
在内存表上构造 TPCH 风格 orders(10 万行)与
customer(5000 行),DuckDB
1.5.4 的 EXPLAIN
片段(经删减):
HASH_JOIN
Conditions: o_custkey = c_custkey
SEQ_SCAN memory.tpch.orders
Projections: o_custkey, o_totalprice
Filters: o_orderdate>='1996-06-01'::DATE
SEQ_SCAN memory.tpch.customer
Projections: c_custkey, c_name
可见 optimizer 在 scan 上做了 投影裁剪(只读 join/agg 需要的列)与 谓词下推(日期过滤在 orders scan)。无 shuffle:两表在同一进程 hash join。同一查询 3 次 wall time 中位数 0.0025 s(内存表、冷启动后,本机 WSL2)。
4.2 宽 Parquet:投影下推对 IO 的影响
写 20 列 × 50 万行
Parquet(row_group_size=100000,文件约
3.65 MiB),对比:
| 查询 | EXPLAIN ANALYZE 扫描行数(filter
场景) |
5 轮中位 wall(ms) |
|---|---|---|
count(*) 全表 |
— | 0.67 |
count(col_0) 单列 |
— | 0.48 |
count(*) + col_0 窄区间 |
1000 rows(过滤后) | 2.32 |
第三组在 scan 算子上出现 Total Files Read: 1
与 1000 rows,说明 row group 级 min/max
跳过 生效(与 lakehouse/18
实验二 同一机制)。单列 count(col_0) 比
count(*) 快,体现 只读一列
chunk 的投影下推——宽表上这是数量级 IO
差异的来源。
4.3 实验边界
- 未做 DuckDB vs Trino 同表 scanned bytes
对照(本机无 Trino)。可复现步骤:用
reproduce/docker-compose.yml起 Trino + Iceberg,与 DuckDBiceberg_scan对同一谓词比较 scan 算子输入;见 第 15 篇。 - 绝对耗时受 page cache 影响;趋势(投影 < 全列、排序/过滤可 skip row group)比毫秒数更稳定。
五、何时仍需要 Trino:嵌入式覆盖不了的场景
DuckDB 能读 Iceberg,不等于替代 Trino:
- 多租户并发:数十 BI 用户同时提交 SQL,需要 coordinator 队列、resource group(第 16 篇)。
- 跨源联邦:一条 SQL join Iceberg 表与 MySQL / Hive / Kafka 连接器——Trino Connector SPI 的设计目标。
- 分布式 dynamic filter:大事实表 join 小维表,build 侧 key 运行时下推到 probe 侧 scan(Trino 481 docs);单机引擎形态不同,收益模型也不同。
- 数据大于单机内存且无法局部裁剪:partition/file pruning 失效时,嵌入式只能垂直扩容;MPP 可水平加 Worker。
反过来,ETL 脚本内嵌聚合、笔记本 EDA、CI 里对湖表做 smoke query、无 JVM 环境读 Iceberg——DuckDB 运维成本更低。
六、小结
- DuckDB 是完整的嵌入式 OLAP 产品:向量化 + morsel 并行 + 官方 Iceberg/Delta 扩展;与 Trino 差在 无 MPP shuffle、无多租户 SQL 服务。
- DataFusion 是 可嵌入的 Rust 引擎库,Arrow 原生,Iceberg 依赖社区 crate;适合自研引擎底座,不是 DuckDB 的直接替代品。
- 本机 DuckDB 1.5.4 实验显示:optimizer 在 scan 层做投影与谓词下推;Parquet 窄投影与 row group 跳过可观测。
- 读湖 四层漏斗 见 lakehouse/18;planner 哪一步注入 layout constraint 见 第 15 篇。
返回 系列目录
参考资料
- DuckDB Documentation, Internals overview / Vectorized Execution / Lakehouse Formats / Iceberg Extension(A 级)。
- Apache DataFusion Documentation, Concepts / Physical Optimizer(A 级)。
- Trino Documentation 476+, Overview Architecture、Dynamic filtering(引用,未本机实测)。
- 本机实验:DuckDB 1.5.4,Python 3.14 venv,WSL2;脚本
post/db/query-engine/reproduce/run_duckdb_experiments.py与宽表 Parquet 辅助脚本(同环境一次性执行)。
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【分布式 OLAP 查询引擎】向量化批处理与 Morsel-Driven 并行
拆解列向量 batch、SelectionVector 与 flat/dictionary 编码;对照 columnar-engine/04 的 ClickHouse Block 直觉,说明 DuckDB morsel-driven 与 Trino Page 流在 MPP 上的落地,并给出本机 DuckDB 1.5.4 实测。
【分布式 OLAP 查询引擎】Trino · Spark · DuckDB · 优化与 MPP 执行
闭合数据平台栈最后一块:从 SQL 解析与 Calcite 式优化,到 Volcano/向量化执行、Trino Coordinator/Worker 与 shuffle,再到 Iceberg connector 下推与生产排查。承接 lakehouse 第 18 章读湖视角,补全「谁在做 planning」的引擎内核层。
【分布式 OLAP 查询引擎】OLAP 查询引擎全景:从单进程到 MPP
从 OLTP/OLAP/HTAP 边界、嵌入式 DuckDB 与分布式 Trino/Spark 分工、批式扫描与交互式查询延迟口径出发,闭合 lakehouse 与 stream-processing 之间的查询层缺口,并给出本系列 18 篇地图。
【分布式 OLAP 查询引擎】Iceberg 下推全链路:Planner 视角
与 lakehouse/18 分工:那边讲四层读湖漏斗是什么;本篇讲 Trino/Spark/DuckDB 在 SQL 优化链的哪一步把谓词变成 layout constraint、谁调用 Iceberg planning、split 如何携带残余谓词。引用官方文档与 lakehouse/18 本机 PyIceberg 实测,不伪造 Trino 计划输出。