土法炼钢 · 系统与基础设施

【列存引擎内核】DuckDB 向量化与 Morsel-Driven Pipeline

文章导航

分类入口
databasearchitecture
标签入口
#duckdb#vectorized-execution#morsel-driven#pipeline#parallel#hash-join

目录

MonetDB/X100 确立的 向量批执行 被 DuckDB 与 ClickHouse 共同继承;DuckDB 在并行层采用 morsel-driven(HyPer 论文传统):Row Group 切成 morsel,线程池动态抢任务,而非静态 partition。本文从物理算子讲到 src/parallel/executor.cpp,并对照 ClickHouse 向量化(第 04 篇)PostgreSQL 执行器 的 volcano 逐行模型。

版本锚定:DuckDB 1.x


一、Volcano vs Vector vs Morsel

模型 数据单位 并行 代表
Volcano 1 row PG 传统 plan
Vector batch(如 1024 行) 算子内 SIMD MonetDB/X100
Morsel-driven batch + 动态 work queue 核间负载均衡 HyPer, DuckDB

PostgreSQL 11+ 有部分 vectorization,但 OLTP 主路径仍是 tuple-at-a-time;DuckDB 纯分析 全路径向量 + morsel。

flowchart TB
  subgraph vol [Volcano PG]
    N1[Next tuple loop]
  end
  subgraph vec [Vector CH/DuckDB]
    B[Column batch]
    SIMD[SIMD filter/aggr]
    B --> SIMD
  end
  subgraph mor [Morsel DuckDB]
    Q[Task queue]
    W1[Worker]
    W2[Worker]
    Q --> W1
    Q --> W2
  end

二、Column Vector 与 Selection Vector

DuckDB DataChunk(概念名,源码见 src/common/types/data_chunk.cpp)持有:

Filter 典型路径:

  1. 比较产生 SelectionVector
  2. 下游算子只处理选中行;
  3. 常配合 字典编码 列避免物化。

ClickHouse ColumnUInt8 filter 类似,实现在 src/Columns/——第 04 篇 已述 Block 结构。


三、Physical Operator 与 Pipeline

物理计划树分解为 Pipeline:线性算子链 + 一个 Source + 一个 Sink

flowchart LR
  SRC[Table Scan Source]
  F[Filter]
  P[Projection]
  H[Hash Join Probe]
  A[Hash Aggregate]
  SNK[Result Collector Sink]
  SRC --> F --> P --> H --> A --> SNK

源码入口:


四、Morsel-Driven 并行

4.1 划分

Table Scan 按 Row Group → morsel(默认与 Row Group 对齐或可再切)。每个 morsel 是一个 ParallelTask

4.2 调度

TaskScheduler 维护 worker 线程;任务完成 push 新 morsel。负载均衡:慢 morsel(高 selectivity 后仍大)不阻塞快线程。

对比 ClickHouse:

4.3 threads setting

SET threads = 8;

超过 CPU 核数可能因 contention 变慢——须本地 benchmark,本文不给倍数。


五、Hash Join 实现要点

DuckDB 默认 hash join(无 index nested loop 对事实表大 scan 不友好)。

Build 侧:

Probe 侧:

ClickHouse ConcurrentHashJoin / grace hash 在 src/Processors/——分布式时还有 GLOBAL 广播(第 09 篇)。


六、Aggregation

6.1 单层聚合

PhysicalHashAggregate:hash table 键 = GROUP BY 列,值 = aggregate state。

6.2 两阶段 / 分区

高 cardinal GROUP BY:

ClickHouse group_by_two_level_threshold 同族思路——配置(第 16 篇)

6.3 DISTINCT

Often GROUP BY + COUNT 或专用 PhysicalDistinct——优化器 rewrite。


七、Optimizer 与 Pipeline 的衔接

src/optimizer/ 规则影响物理 plan:

EXPLAIN SELECT ...;
EXPLAIN ANALYZE SELECT ...;

EXPLAIN ANALYZE 输出 实际 timing——仅本地有效,不可跨文引用具体毫秒。


八、Table Scan 与 Segment Skip

ColumnSegment 时:

  1. 读 segment footer statistics;
  2. max < predicate constant → skip entire segment;
  3. 否则 decompress + vector filter。

与 ClickHouse Mark Range + 跳数索引(第 07 篇) 同族 zonemap pruning

Parquet 扫描复用 row group metadata——read_parquet 不经过 DuckDB storage 仍享 skip。


九、Spill 与内存

Settings:

Setting 作用
memory_limit 进程软上限
max_temp_directory_size spill 上限
preserve_insertion_order 是否保序(影响并行)

OOM 时 DuckDB 尝试 spill;仍失败则报错——嵌入式 Python 需 catch。

ClickHouse OOM 见 max_memory_usage经典故障(第 15 篇)


十、并行 COPY 与 INSERT

COPY large_table FROM 'big.csv' (HEADER, DELIMITER ',');

Parser + 向量化 insert 可并行读文件——瓶颈常在磁盘或 CSV parse。

对比 ClickHouse INSERT FORMAT CSV + ingest 背压——DuckDB 单机 无 parts_to_throw_insert,但超大事务 checkpoint 压力存在。


十一、UDF 与向量边界

用户标量 UDF 若未向量化,可能 逐行回调 Python——destroy 性能。推荐:

ClickHouse 同理:Python UDF 慢于原生 executable 或 SQL。


十二、与 ClickHouse Processors 对照

概念 DuckDB ClickHouse
DataChunk Block
Pipeline + MetaPipeline Processor DAG
并行 Morsel task queue max_threads, parts
远程 RemoteQueryExecutor
EXPLAIN EXPLAIN ANALYZE EXPLAIN PLAN, query_log

读 CH 源码 src/Processors/Executors/ExecutingGraph.cpp 与 DuckDB src/parallel/executor.cpp 可感受 调度哲学差异


十三、PostgreSQL 执行器对照

PG 执行器(第 12 篇 PG) ExecProcNode 逐 plan node 递归;DuckDB 编译为 扁平 pipeline + 向量 primitive。PG 向 OLAP 的 columnar store(cstore_fdw 等)不在 PG 核内——pg_duckdb 是旁路加速。


十四、Profiling

PRAGMA enable_profiling;
SELECT ...;
PRAGMA profiling_output = 'query.json';

Chrome trace 风格分析 morsel 并行度——工具随版本演进,以文档为准。

ClickHouse 用 query_logtrace_log监控(第 14 篇))。


十五、实验框架(须本地执行)

15.1 并行度对比

SET threads=1;
SELECT sum(v) FROM big WHERE g > 0;
SET threads=8;
SELECT sum(v) FROM big WHERE g > 0;

记录 EXPLAIN ANALYZE 中 total time——仅本地

15.2 Join spill

SET memory_limit='128MB';
SELECT count(*) FROM big a JOIN big b ON a.id = b.id;

观察是否 spill 或失败。


十六、学术谱系:X100 → Morsel → DuckDB Pipeline

阶段 文献 / 系统 贡献
2005 MonetDB/X100, CIDR 向量批执行奠基(与 第 04 篇 同源)
2014 Leis et al., Morsel-Driven Parallelism, SIGMOD 动态任务窃取;抗 skew;HyPer 传统
2018 Kersten et al., PVLDB compiled vs vectorized 实证争论
2019+ DuckDB 全路径向量 + morsel;嵌入式进程内(Raasveldt & Mühleisen, SIGMOD 2019 demo)

与 ClickHouse 对照:CH 用 PipelineExecutor 调度 IProcessor(第 04 篇);DuckDB 用 morsel 队列在 核间 抢 Row Group 切片。两者都属 X100 谱系,并行调度哲学不同——不是「谁更向量化」。

16.1 争论:静态分区 vs morsel

DuckDB 选后者服务 单机多核分析;CH 服务端还要面对 多查询 + merge 线程 争抢——并行模型不可直接照搬。

16.2 工程间隙

论文 / HyPer 设定 DuckDB 1.x 嵌入
独占服务器、长查询 notebook / 应用进程共享内存
少谈持久化 merge .duckdb checkpoint 与 CH Part merge 不同账单
理想 SIMD 对齐 Selection Vector + 字典列使路径分支更多
无多租户 单文件写锁限制并发 ingest

16.3 开放问题

  1. Spill 阈值与 morsel 粒度如何联合调优? 可检验:降 memory_limitEXPLAIN ANALYZE 的 spill 与线程空闲(入口:Leis SIGMOD’14;DuckDB Memory Management)。
  2. 嵌入式场景是否应默认更激进 codegen? 与第 04 篇 CH 问题对称;Kersten 2018 仍是坐标。
  3. 与 CH Processors 在同一硬件上的并行效率——须固定 query mix 实测,本文不排名。

十七、小结

DuckDB 执行层 = 向量批 + morsel-driven 任务窃取;优化器把 skip 下推到 Segment/Parquet。理解 Pipeline breaker 与 spill,就理解嵌入式 OLAP 在 单机核数 上的扩展上限。

附录 A、物理算子目录(源码)

算子 路径方向
Table Scan operator/scan/physical_table_scan.cpp
Filter operator/filter/physical_filter.cpp
Hash Join operator/join/physical_hash_join.cpp
Cross Product operator/join/physical_cross_product.cpp
Hash Aggregate operator/aggregate/physical_hash_aggregate.cpp
Window operator/aggregate/physical_window.cpp
Order By operator/order/physical_order.cpp
Limit operator/limit/physical_limit.cpp
Insert operator/persistent/physical_insert.cpp

附录 B、Pipeline Breaker 列表

Hash Join Build、Hash Aggregate、Sort、部分 Window 会 barrier——先完成 build 再 probe/输出。理解 breaker 数量解释 为何某些查询并行度低

附录 C、Grace Hash Join

Build 侧大于 memory 时分区 spill 到 temp_directory,逐分区 join。ClickHouse 类似 grace hash in processors。调小 memory_limit 可强制 spill 测试(测试库)。

附录 D、Optimizer 规则(代表性)

Filter pushdown、Join reorder、Common subexpression、Top-N pushdown、Duplicate grouping removal。EXPLAIN 输出 logical → physical 变化。

上一篇DuckDB 架构

下一篇ClickHouse vs DuckDB 选型

参考资料

核心论文

  1. Boncz et al., MonetDB/X100: Hyper-Pipelining Query Execution, CIDR 2005(A 级)。
  2. Leis et al., Morsel-Driven Parallelism: A NUMA-Aware Query Evaluation Framework for the Many-Core Age, SIGMOD 2014(A 级)。
  3. Kersten et al., Everything You Always Wanted to Know About Compiled and Vectorized Queries…, PVLDB 2018(A 级)。
  4. Raasveldt & Mühleisen, DuckDB: an Embeddable Analytical Database, SIGMOD 2019 demo(B/A 级系统介绍)。

规范 / 源码 / 文档

  1. DuckDB Documentation, Execution / Parallelism(1.x;A 级)。
  2. DuckDB Source, src/execution/src/parallel/(A 级)。
  3. ClickHouse Source, src/Processors/(对照;A 级)。

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。

2026-07-07 · database / distributed

【分布式 OLAP 查询引擎】引擎选型与数据平台阅读地图

用决策树收束 Trino/Spark/ClickHouse/DuckDB/DataFusion/PostgreSQL 的适用边界:交互式联邦、批 ETL、嵌入式分析、流批一体各走哪条路径;给出能力对照表(无吞吐排名)与 postgresql→columnar→lakehouse→stream→query-engine 全栈阅读顺序,闭合数据平台栈。


By .