DuckDB 向量化与 Morsel-Driven Pipeline
DuckDB åéåä¸ Morsel-Driven Pipeline
ltl 2026-08-20 12 é 读6åéåæåå¸äº quant67.comï¼è½¬è½½è¯·ä¿çåºå¤ã
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ï¼ææï¼
- å¤å
Vectorï¼ - è¡æ°
countï¼ - å¯é Selection Vectorï¼filter åæ´»è·è¡ç´¢å¼ï¼é¿å ç©çå é¤ï¼ã
Filter å ¸åè·¯å¾ï¼
- æ¯è¾äº§ç
SelectionVectorï¼ - 䏿¸¸ç®ååªå¤çéä¸è¡ï¼
- 常é å åå ¸ç¼ç åé¿å ç©åã
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
- Pipeline breakerï¼Hash Join BuildãHash Aggregate çä¼ Barrierââå 宿 build å probeã
- Meta-Pipelineï¼å¤æ JOIN æ å¤ pipeline ç±
Executorè°åº¦é¡ºåºã
æºç å ¥å£ï¼
src/execution/physical_operator.cppsrc/execution/operator/scan/physical_table_scan.cppsrc/execution/operator/join/physical_hash_join.cppsrc/execution/operator/aggregate/physical_hash_aggregate.cpp
åãMorsel-Driven å¹¶è¡
4.1 åå
Table Scan æ Row Group â morselï¼é»è®¤ä¸ Row Group 坹齿å¯ååï¼ãæ¯ä¸ª morsel æ¯ä¸ä¸ª ParallelTaskã
4.2 è°åº¦
TaskScheduler ç»´æ¤ worker 线ç¨ï¼ä»»å¡å®æ push æ° morselãè´è½½åè¡¡ï¼æ
¢ morselï¼é« selectivity åä»å¤§ï¼ä¸é»å¡å¿«çº¿ç¨ã
å¯¹æ¯ ClickHouseï¼
- CHï¼
max_threads+ parts çº§å¹¶è¡ + MergeTree range è¯»ï¼ - DuckDBï¼å表 scan ä¹ morsel å¹¶è¡ï¼æ shardã
4.3 threads setting
SET threads = 8;
è¶ è¿ CPU æ ¸æ°å¯è½å contention åæ ¢ââé¡»æ¬å° benchmarkï¼æ¬æä¸ç»åæ°ã
äºãHash Join å®ç°è¦ç¹
DuckDB é»è®¤ hash joinï¼æ index nested loop 对äºå®è¡¨å¤§ scan ä¸å好ï¼ã
Build ä¾§ï¼
- åéå insert hash tableï¼
- åç¬¦ä¸²ç¨ salted hashï¼
- 大 build å¯ external hash join spill å°
temp_directoryã
Probe ä¾§ï¼
- morsel å¹¶è¡ probeï¼
- semi/anti join ä¼åæ¶é¤ä¸å¿ è¦åã
ClickHouse ConcurrentHashJoin / grace hash å¨ src/Processors/ââåå¸å¼æ¶è¿æ GLOBAL 广æï¼ç¬¬ 09 ç¯ï¼ã
å ãAggregation
6.1 åå±èå
PhysicalHashAggregateï¼hash table é® = GROUP BY åï¼å¼ = aggregate stateã
6.2 䏤鶿®µ / ååº
é« cardinal GROUP BYï¼
- å partition å partial aggregateï¼
- spill 类似 joinã
ClickHouse group_by_two_level_threshold åææè·¯ââé
ç½®ï¼ç¬¬ 16 ç¯ï¼ã
6.3 DISTINCT
Often GROUP BY + COUNT æä¸ç¨ PhysicalDistinctââä¼åå¨ rewriteã
ä¸ãOptimizer ä¸ Pipeline çè¡æ¥
src/optimizer/ è§åå½±åç©ç planï¼
- Predicate pushdown å° scanï¼Parquet row group statsï¼ï¼
- Join order 卿è§å / å¯åå¼ï¼
- Common subexpression eliminationï¼
- Top-N 䏿¨ï¼LIMIT + ORDER BYï¼ã
EXPLAIN SELECT ...;
EXPLAIN ANALYZE SELECT ...;
EXPLAIN ANALYZE è¾åº å®é
timingââä»
æ¬å°ææï¼ä¸å¯è·¨æå¼ç¨å
·ä½æ¯«ç§ã
å «ãTable Scan ä¸ Segment Skip
读 ColumnSegment æ¶ï¼
- 读 segment footer statisticsï¼
- è¥
max < predicate constantâ skip entire segmentï¼ - å¦å 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 æ§è½ãæ¨èï¼
- DuckDB åç SQL / å®ï¼
- Arrow é¶æ·è´æ¥å£ï¼
- æ©å± C++ UDFã
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_logãtrace_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
- éæ partitionï¼å®ç°ç®åï¼skew æ¶æ ¸ç©ºè½¬ã
- Morsel / work stealingï¼Leis 2014ï¼ï¼è´è½½å衡好ï¼è°åº¦ä¸ç¼åå±é¨æ§æä»£ä»·ã
DuckDB éåè æå¡ åæºå¤æ ¸åæï¼CH æå¡ç«¯è¿è¦é¢å¯¹ 夿¥è¯¢ + merge çº¿ç¨ äºæ¢ââå¹¶è¡æ¨¡åä¸å¯ç´æ¥ç §æ¬ã
16.2 å·¥ç¨é´é
| 论æ / HyPer è®¾å® | DuckDB 1.x åµå ¥ |
|---|---|
| ç¬å æå¡å¨ãé¿æ¥è¯¢ | notebook / åºç¨è¿ç¨å ±äº«å å |
| å°è°æä¹ å merge | .duckdb checkpoint ä¸ CH Part merge ä¸åè´¦å |
| çæ³ SIMD å¯¹é½ | Selection Vector + åå ¸å使路å¾åæ¯æ´å¤ |
| æ å¤ç§æ· | åæä»¶åééå¶å¹¶å ingest |
16.3 弿¾é®é¢
- Spill éå¼ä¸ morsel ç²åº¦å¦ä½èåè°ä¼ï¼ 坿£éªï¼é
memory_limitåEXPLAIN ANALYZEç spill ä¸çº¿ç¨ç©ºé²ï¼å ¥å£ï¼Leis SIGMOD'14ï¼DuckDB Memory Managementï¼ã - åµå ¥å¼åºæ¯æ¯å¦åºé»è®¤æ´æ¿è¿ codegenï¼ ä¸ç¬¬ 04 ç¯ CH é®é¢å¯¹ç§°ï¼Kersten 2018 仿¯åæ ã
- ä¸ 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 éå
åèèµæ
æ ¸å¿è®ºæ
- Boncz et al., MonetDB/X100: Hyper-Pipelining Query Execution, CIDR 2005ï¼A 级ï¼ã
- Leis et al., Morsel-Driven Parallelism: A NUMA-Aware Query Evaluation Framework for the Many-Core Age, SIGMOD 2014ï¼A 级ï¼ã
- Kersten et al., Everything You Always Wanted to Know About Compiled and Vectorized Queriesâ¦, PVLDB 2018ï¼A 级ï¼ã
- Raasveldt & Mühleisen, DuckDB: an Embeddable Analytical Database, SIGMOD 2019 demoï¼B/A 级系ç»ä»ç»ï¼ã
è§è / æºç / ææ¡£
- DuckDB Documentation, Execution / Parallelismï¼1.xï¼A 级ï¼ã
- DuckDB Source,
src/execution/ãsrc/parallel/ï¼A 级ï¼ã - ClickHouse Source,
src/Processors/ï¼å¯¹ç §ï¼A 级ï¼ã
Aitishiku.com