存储层把 Part 读成 列向量(第 2 篇);执行层必须在 batch 上算,否则函数调用与 cache miss 吃掉列存带宽优势。ClickHouse 官方 Architecture 明确:operations dispatch on arrays (vectors or chunks of columns)。
本文拆解 Block、IProcessor pipeline,并对照
PG
执行器火山模型。版本:24.x LTS。
一、为什么需要向量化
Volcano:每算子 next() 返回一行 → 虚函数 +
分支多,CPU 流水线难满。
向量:一次处理 \(N\)
行(max_block_size,常见 65536)→ filter
生成位图、aggregate 批量 update。
MonetDB/X100(CIDR 2005)系统论证了 vectorized + cache-conscious 对 OLAP 的必要性;ClickHouse 属同一设计谱系。
flowchart LR
VOL[Volcano 逐行] --> CH[ClickHouse 逐 Block]
CH --> SIMD[SIMD 友好]
二、Block 与 Column
Block(src/Core/Block.h)= 列名
+ ColumnWithTypeAndName 数组 + 行数。
| 类型 | 头文件 | 说明 |
|---|---|---|
ColumnVector<T> |
ColumnVector.h |
数值 POD 数组 |
ColumnString |
ColumnString.h |
偏移 + chars |
ColumnNullable |
ColumnNullable.h |
null map + nested |
ColumnLowCardinality |
ColumnLowCardinality.h |
字典列 |
IColumn 接口:clone、insert、filter、permute
等,算子间传递 ColumnPtr。
三、IProcessor 与 Pipeline
IProcessor(Processors/IProcessor.h):
prepare():声明需要输入端口 / 输出就绪work():消费 Chunk,产生 Chunk- 端口连接构成 DAG
PipelineExecutor 多线程调度
ready 处理器,减少全局同步(官方
Architecture Processors 节)。
flowchart TB
SCAN[MergeTreeSelect] --> PRE[Prewhere]
PRE --> FIL[FilterTransform]
FIL --> EXP[ExpressionTransform]
EXP --> AGG[AggregatingTransform]
AGG --> OUT[Output]
四、读存储:MergeTreeSource
MergeTreeSelectProcessor / read pool 从 Part
读列 → 组装 Block → 下游算子。与 第 5
篇 衔接。
五、Filter 与 Project
FilterTransform:对条件列求值 →
UInt8 过滤 mask → IColumn::filter
裁剪各列。
ExpressionTransform:ExpressionActions
将 AST 编译为对列的循环(非常量折叠时)。
六、Aggregate
AggregatingTransform:按 key 列
hash,批量更新 AggregateFunction 状态(非逐行
transition)。GROUP BY +
高基数时内存受
max_bytes_before_external_group_by 等限制。
七、与 PG 执行器对照
| 机制 | PostgreSQL | ClickHouse |
|---|---|---|
| 驱动 | ExecutorRun 循环
ExecProcNode |
PipelineExecutor |
| 数据 | TupleTableSlot |
Block |
| 表达式 | EEO opcode | ExpressionActions |
| Hash Join | 逐行 build/probe | 批量 |
| 并行 | parallel worker 部分节点 | pipeline + read pool |
PG 向量化改进(如 bulk read)存在,但 OLTP 兼容约束下仍以行为主;ClickHouse 默认即 batch。
八、Settings 与调优
| Setting | 含义 |
|---|---|
max_block_size |
单 Block 行数上限 |
min_insert_block_size_rows |
insert 最小 batch |
max_threads |
执行线程 |
compile_expressions |
LLVM JIT 表达式(视版本) |
九、实验说明
本环境无 ClickHouse。读者可用:
EXPLAIN PIPELINE SELECT count() FROM table WHERE ...;观察 processor 链名称。不粘贴未执行输出。
十、小结
列存执行核心:Block 列向量 + Processor DAG + 批量算子。读路径产出 Block,算子链在 Block 上向量化,直到输出客户端。
上一篇:压缩与编码
下一篇:查询读取路径
附录、扩展阅读与工程注记
Block 与 Chunk
Block(Core/Block.h)含多列;Pipeline
中 Chunk 携带 Block 或列子集 +
行数。max_block_size 控制单行算子输出规模。
IProcessor 状态机
prepare() 返回 NeedData /
Ready /
Finished;work() 消费输入
Chunk、产出输出 Chunk。PipelineExecutor
多线程调度 ready 节点,避免全局锁(官方 Architecture
Processors)。
与 PG 执行器对照
| PG | ClickHouse |
|---|---|
ExecProcNode 拉一行 |
IProcessor::work 拉一批 |
TupleTableSlot |
Block / ColumnVector |
ExprState EEO |
ExpressionActions 列循环 |
nodeHashjoin.c 逐行 probe |
HashJoin transform 批量 |
SIMD
Columns/Common/Simd* 与函数实现(如
FunctionsArithmetic)对固定宽度类型使用
intrinsics;具体是否触发取决于类型与编译标志(-march)。
Mark Range 算法(概念)
对每个 Part:
- 用
primary.idx二分/扫描,找与 PK 谓词相交的 granule 下标区间 \([g_{lo}, g_{hi}]\)。 - 跳数索引进一步缩小区间(第 7 篇)。
- 对每列 Mark 文件取 \([g_{lo}, g_{hi}]\) 对应压缩块并解压。
- PREWHERE 列优先读,结果位图过滤后再读其余列(若优化器下推)。
PREWHERE 语义
PREWHERE
不是语法糖:优化器将选择性高的条件移到存储层,减少列
IO。官方建议高选择性条件放 PREWHERE;WHERE 其余
predicate 在内存 Block 上执行。
并行
- Part 级:
MergeTreeReadPool分配 Part 给线程。 - Granule 级:Mark Range 切分。
max_threads与max_streams_to_max_threads_ratio共同约束。
分布式 DDL 边界
ReplicatedMergeTree 上 ON CLUSTER DDL 通过
distributed DDL queue;与 Part 级复制不同层。第 8 篇聚焦
Part 同步。
Keeper 与 ZooKeeper 差异
24.x 推荐 Keeper(Raft);ZK 路径约定兼容。新集群优先 Keeper,减少 JVM 依赖与 tail latency。
Quorum insert
insert_quorum 要求 N 副本确认 Part;与 async
insert 策略互斥需谨慎。金融场景可能启用,吞吐下降。
Recovery 线程
副本 system.replication_queue 中
GET_PART 失败会重试;Broken Part
需人工 SYSTEM DROP REPLICA / 重新同步。监控
last_exception。
与 PG 外表对比
PostgreSQL 作源时,MaterializedPostgreSQL 或
CDC 工具写入 MergeTree;PG 仍行存 MVCC,CH 侧 append
Part。一致性窗口由复制协议决定。
与 observability ingest
日志写入 CH 常用 Kafka 引擎 + MV(第 10 篇规划)。ingest 侧 batch 大小直接决定 Part 尺寸——与 可观测性系列 管道设计联动。
CPU vs IO bound 判定
高压缩率 + 宽 granule 可能 CPU bound(解压);NVMe
上低压缩可能 IO bound。system.events 中
OSIOWait* vs UserTime
辅助判断,需本机 profile。
max_threads 与 cores
max_threads 默认与 CPU 核相关;过大线程增加
Part 并行读开销与内存。HTAP 混部应限制 CH 查询线程池。
内存跟踪
system.metrics 的
MemoryTracking、MergesMutationsMemoryTracking;OOM
前常见 merge 与 big aggregation 同抢内存。
Part 命名与 mutation
mutation 产生 xxx_mutyyy 中间 Part;完成后旧
Part outdated。system.mutations 的
parts_to_do 反映剩余工作量。
Collapsing 与 VersionedCollapsing
VersionedCollapsing 用 (Sign, Version)
对消;比纯 Collapsing 更适合乱序变更。merge
前查询仍需应用层理解 sign。
SummingMergeTree 列和
仅数值列默认 sum;非键列需显式指定 summing 列。非 sum 列取任意值(merge 确定性规则见文档)。
AggregatingMergeTree 状态
存 AggregateFunction 状态;查询需
-Merge 组合器。适合预聚合管道,schema
设计门槛高。
GraphiteMergeTree rollup
按时间精度 rollup 规则在 config 定义;监控迁移场景专用。与普通 Summing 不同。
S3 磁盘与冷存
S3 作 disk 时 Part 对象化;读延迟高于本地
SSD。merge 仍发生,网络带宽成为瓶颈。
Replicated 与 S3
零拷贝 replication 到 S3 磁盘配置见官方;副本 fetch 可走对象存储。与第 8 篇 queue 类型相关。
数据倾斜与 PARTITION BY
单分区过大 merge 慢;过多分区 metadata
膨胀。按天/租户合理切分;避免
PARTITION BY rand()。
PRIMARY KEY 长度
PK 列过多/long String 增大 primary.idx
与内存。仅前缀必要列;其余放 ORDER BY 后缀或跳数索引。
跳数索引 GRANULARITY 选择
GRANULARITY 过大索引粗;过小索引体积接近逐 granule。默认 1–4 需按列选择性实验(第 7 篇)。
bloom_filter 假阳性
Bloom 仅跳过 granule;假阳性多读 granule 仍正确。无 false negative。
minmax 索引失效场景
列值在 granule 内分布宽但谓词窄,minmax 无法跳过——需更细 ORDER BY 或 bloom。
set 索引大小上限
set 索引 granule 内 distinct 超上限则退化;高基数列不适用 set。
向量索引(边界)
向量相似搜索非 MergeTree 经典跳数索引范畴;24.x 实验特性不在此系列承诺。
Pipeline EXPLAIN 用法
EXPLAIN PIPELINE 展示 Processor 图;与
EXPLAIN indexes=1
互补。本环境无实例不贴输出。
QueryPlan 阶段
Analyzer 产出 QueryPlan,再转 Pipeline。PREWHERE 下推在此阶段决定;SQL 写法影响是否自动 PREWHERE。
Constant 折叠与 primary key
常量谓词与 PK 范围交集在优化期计算;参数化查询仍受益 prepared 边界。
FINAL 与 dedup
ReplacingMergeTree 的 FINAL
在查询期归并;替代方案 MV 去重或应用层
argMax。
Lightweight DELETE(版本边界)
较新版本 lightweight delete 标记删除 granule;与 mutation 路径不同。以 24.x 文档为准是否 GA。
Transaction 语义边界
单 insert block 原子;跨 Part 无 MVCC 快照隔离。勿与 PG MVCC 类比。
chDB / clickhouse-local
嵌入式 clickhouse-local 适合 Part
格式实验(第 2 篇);与 server 共用 MergeTree 代码路径。
版本升级与 Part 兼容
大版本升级前查 release notes Part 格式变更;通常向后读旧 Part,merge 逐步重写。
checksum 算法
checksums.txt 使用 CityHash128 等;损坏 Part
拒绝加载保护下游。
Serialization 与类型变更
ALTER MODIFY COLUMN 可能触发 mutation 重写;类型不兼容需中间列。
Dictionary 与外部维表
字典非 MergeTree Part;JOIN 字典与 MergeTree scan 是不同 IO 路径。
Global IN 代价预告
Distributed 上 GLOBAL IN 广播维表(第 9 篇);本地 MergeTree 无此问题。
测试数据 hits 样本
官方 hits 数据集适合验证读路径;下载与导入步骤见 clickhouse.com/docs getting started。
benchmark 伦理
引用外部 benchmark 必须标注来源;自测需 3 轮中位数与环境表(WRITING_GUIDE)。
源码阅读顺序建议
MergeTree:StorageMergeTree →
IMergeTreeDataPart →
MergeTreeReaderWide →
MergeTreeDataMergerMutator。执行:PipelineExecutor
→ IProcessor。
社区与 LTS
24.x LTS 安全/backport 周期见官网;生产锚定 LTS 而非 latest。
system.parts 字段解读
运维读 Part
状态时常用列:database、table、name(目录名)、part_type(Wide/Compact)、rows、bytes_on_disk、bytes_on_disk_uncompressed、primary_key_bytes_in_memory、marks_bytes_on_disk、level、data_version、is_frozen。active=0
表示已被 merge 替换但未物理删除。与 LSM SST 层数
类似,应监控每表 active part 数趋势。
system.merges 与 merge 进度
system.merges 展示当前运行中的
merge:database、table、elapsed、progress(0–1)、num_parts、result_part_name、total_size_bytes_uncompressed。长时间
progress 不动可能磁盘 IO 饱和或单 Part
过大。merge_max_block_size
影响单次归并行数。
system.query_log 读路径指标
启用 query_log
后,read_rows、read_bytes、result_rows
对比可验证索引剪枝是否生效。PREWHERE 优化通常降低
read_bytes 而 read_rows 仍含
granule 内过滤前行数。本系列不在此环境给出样本数值。
MergeTreeWriteSettings 与 insert
除表级 merge_tree settings 外,insert 受
max_insert_block_size、min_insert_block_size_rows、async_insert(24.x
异步 insert 特性以文档为准)影响。小 block 直接对应小
Part——平台侧应强制 batch 或 Buffer 引擎。
StoragePolicy 与多磁盘
TTL MOVE 与 storage_policy 可将 Part
移至慢盘/对象存储。merge 与 fetch
路径需保证目标卷有足够空间;system.disks 与
system.storage_policies 描述卷与策略。
Projection 与读优化(边界)
24.x 支持 projection 预聚合/排序副本。属于高级 DDL,本系列主路径不展开;知晓其存在可避免与跳数索引职责混淆——projection 是额外 Part 子集,非跳数索引。
Sample By 与近似查询
SAMPLE BY 与 SAMPLE
子句依赖排序键哈希;与主键剪枝独立。日志采样分析常用,但不替代正确
ORDER BY 设计。
Nullable 与 Default 列存储
Nullable 列额外
.null.bin(视版本/序列化而定);默认值列可能仅
.default 文件。读路径需合并 null map;宽表
Nullable 多会增加文件数。
UUID / IPv6 类型
固定长度类型序列化紧凑;仍受 granule 与 codec 影响。随机 UUID 作 ORDER BY 前缀会导致稀疏索引几乎无效——工程上避免。
DateTime64 与时区
DateTime64 排序键常用 DoubleDelta;插入时区
session_timezone 与显示无关存储。跨时区报表在
SQL 层转换,不在 Part 内改。
Enum 与 LowCardinality
Enum 存整数标签;LowCardinality 字典适合低基数。高基数 String 强行 LowCardinality 字典膨胀,压缩与查询均变差。
Nested 与 JSON 类型
Nested 在磁盘展开为多个子列 Array;JSON 类型(若启用)路径提取有独立序列化。宽 Nested 增加 Part 文件数,merge 成本上升。
Materialized Column
物化列随 insert 计算持久化,占独立
.bin。适合重复表达式,但增加写放大与存储;与 MV
目标表不同。
TTL DELETE vs DROP PARTITION
TTL DELETE 在 merge 时删
granule;ALTER DROP PARTITION 整分区移除
Part。后者运维更干净;前者适合行级过期。
Freeze / UNFREEZE 备份
FREEZE 硬链 Part 到 shadow/
目录做一致性快照;与副本 fetch 互补。恢复需
ATTACH PART 流程,见官方 Backup 文档。
detach / attach part
手动 DETACH PART 将目录移入
detached/,不参与查询与 merge。排障错误 Part
或迁移数据时使用;attach 前需校验 checksums。
并发 insert 与 block 边界
多客户端 insert 各自形成 Part;无跨客户端单 block 合并。高并发小 insert 是 parts 爆炸主因——应用侧 batch 或 async_insert 缓冲。
OPTIMIZE TABLE FINAL
强制 merge 至单 Part(分区内)并应用 Replacing 等语义。生产大表慎用:IO 峰值、长时间锁表语义以文档为准。更适合维护窗口。
system.parts_columns
逐列
data_compressed_bytes、data_uncompressed_bytes、compression_codec——压缩实验应用此表而非猜。见第
3 篇 benchmark 框架。
Mark Cache 与 Primary Index Cache
频繁查询受益
mark_cache、primary_index_cache(配置与
metric 见文档)。冷查询首次读盘仍取决于 Mark/PK 大小。
ReadInOrder 优化
若查询 ORDER BY 与表排序键一致且无大幅改写,优化器可走 ReadInOrder 减少全量排序内存。与 merge 物理排序强相关。
分布式 DDL 边界
ReplicatedMergeTree 上 ON CLUSTER DDL 通过
distributed DDL queue;与 Part 级复制不同层。第 8 篇聚焦
Part 同步。
Keeper 与 ZooKeeper 差异
24.x 推荐 Keeper(Raft);ZK 路径约定兼容。新集群优先 Keeper,减少 JVM 依赖与 tail latency。
Quorum insert
insert_quorum 要求 N 副本确认 Part;与 async
insert 策略互斥需谨慎。金融场景可能启用,吞吐下降。
Recovery 线程
副本 system.replication_queue 中
GET_PART 失败会重试;Broken Part
需人工 SYSTEM DROP REPLICA / 重新同步。监控
last_exception。
与 PG 外表对比
PostgreSQL 作源时,MaterializedPostgreSQL 或
CDC 工具写入 MergeTree;PG 仍行存 MVCC,CH 侧 append
Part。一致性窗口由复制协议决定。
与 observability ingest
日志写入 CH 常用 Kafka 引擎 + MV(第 10 篇规划)。ingest 侧 batch 大小直接决定 Part 尺寸——与 可观测性系列 管道设计联动。
CPU vs IO bound 判定
高压缩率 + 宽 granule 可能 CPU bound(解压);NVMe
上低压缩可能 IO bound。system.events 中
OSIOWait* vs UserTime
辅助判断,需本机 profile。
max_threads 与 cores
max_threads 默认与 CPU 核相关;过大线程增加
Part 并行读开销与内存。HTAP 混部应限制 CH 查询线程池。
内存跟踪
system.metrics 的
MemoryTracking、MergesMutationsMemoryTracking;OOM
前常见 merge 与 big aggregation 同抢内存。
Part 命名与 mutation
mutation 产生 xxx_mutyyy 中间 Part;完成后旧
Part outdated。system.mutations 的
parts_to_do 反映剩余工作量。
Collapsing 与 VersionedCollapsing
VersionedCollapsing 用 (Sign, Version)
对消;比纯 Collapsing 更适合乱序变更。merge
前查询仍需应用层理解 sign。
SummingMergeTree 列和
仅数值列默认 sum;非键列需显式指定 summing 列。非 sum 列取任意值(merge 确定性规则见文档)。
AggregatingMergeTree 状态
存 AggregateFunction 状态;查询需
-Merge 组合器。适合预聚合管道,schema
设计门槛高。
GraphiteMergeTree rollup
按时间精度 rollup 规则在 config 定义;监控迁移场景专用。与普通 Summing 不同。
S3 磁盘与冷存
S3 作 disk 时 Part 对象化;读延迟高于本地
SSD。merge 仍发生,网络带宽成为瓶颈。
Replicated 与 S3
零拷贝 replication 到 S3 磁盘配置见官方;副本 fetch 可走对象存储。与第 8 篇 queue 类型相关。
数据倾斜与 PARTITION BY
单分区过大 merge 慢;过多分区 metadata
膨胀。按天/租户合理切分;避免
PARTITION BY rand()。
PRIMARY KEY 长度
PK 列过多/long String 增大 primary.idx
与内存。仅前缀必要列;其余放 ORDER BY 后缀或跳数索引。
跳数索引 GRANULARITY 选择
GRANULARITY 过大索引粗;过小索引体积接近逐 granule。默认 1–4 需按列选择性实验(第 7 篇)。
bloom_filter 假阳性
Bloom 仅跳过 granule;假阳性多读 granule 仍正确。无 false negative。
minmax 索引失效场景
列值在 granule 内分布宽但谓词窄,minmax 无法跳过——需更细 ORDER BY 或 bloom。
set 索引大小上限
set 索引 granule 内 distinct 超上限则退化;高基数列不适用 set。
向量索引(边界)
向量相似搜索非 MergeTree 经典跳数索引范畴;24.x 实验特性不在此系列承诺。
Pipeline EXPLAIN 用法
EXPLAIN PIPELINE 展示 Processor 图;与
EXPLAIN indexes=1
互补。本环境无实例不贴输出。
QueryPlan 阶段
Analyzer 产出 QueryPlan,再转 Pipeline。PREWHERE 下推在此阶段决定;SQL 写法影响是否自动 PREWHERE。
Constant 折叠与 primary key
常量谓词与 PK 范围交集在优化期计算;参数化查询仍受益 prepared 边界。
FINAL 与 dedup
ReplacingMergeTree 的 FINAL
在查询期归并;替代方案 MV 去重或应用层
argMax。
Lightweight DELETE(版本边界)
较新版本 lightweight delete 标记删除 granule;与 mutation 路径不同。以 24.x 文档为准是否 GA。
Transaction 语义边界
单 insert block 原子;跨 Part 无 MVCC 快照隔离。勿与 PG MVCC 类比。
chDB / clickhouse-local
嵌入式 clickhouse-local 适合 Part
格式实验(第 2 篇);与 server 共用 MergeTree 代码路径。
版本升级与 Part 兼容
大版本升级前查 release notes Part 格式变更;通常向后读旧 Part,merge 逐步重写。
checksum 算法
checksums.txt 使用 CityHash128 等;损坏 Part
拒绝加载保护下游。
Serialization 与类型变更
ALTER MODIFY COLUMN 可能触发 mutation 重写;类型不兼容需中间列。
Dictionary 与外部维表
字典非 MergeTree Part;JOIN 字典与 MergeTree scan 是不同 IO 路径。
参考资料
- ClickHouse Documentation, Architecture Overview
- ClickHouse Source, v24.3,
src/Processors/,src/Columns/,src/Core/Block.h - Boncz et al., MonetDB/X100, CIDR 2005
- PostgreSQL 系列, 执行器
Block 与 Chunk
Block(Core/Block.h)含多列;Pipeline
中 Chunk 携带 Block 或列子集 +
行数。max_block_size 控制单行算子输出规模。
IProcessor 状态机
prepare() 返回 NeedData /
Ready /
Finished;work() 消费输入
Chunk、产出输出 Chunk。PipelineExecutor
多线程调度 ready 节点,避免全局锁(官方 Architecture
Processors)。
与 PG 执行器对照
| PG | ClickHouse |
|---|---|
ExecProcNode 拉一行 |
IProcessor::work 拉一批 |
TupleTableSlot |
Block / ColumnVector |
ExprState EEO |
ExpressionActions 列循环 |
nodeHashjoin.c 逐行 probe |
HashJoin transform 批量 |
SIMD
Columns/Common/Simd* 与函数实现(如
FunctionsArithmetic)对固定宽度类型使用
intrinsics;具体是否触发取决于类型与编译标志(-march)。
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【列存引擎内核】列存基础与 ClickHouse 架构
行存 vs 列存的带宽、压缩与向量化三角;ClickHouse Server 进程模型、线程池与 MergeTree 引擎家族地图;src/Storages 与 src/Processors 源码入口。对照 PG 行存与 LSM 写优化路径,版本锚定 ClickHouse 24.x LTS。
【列存引擎内核】MergeTree Part 文件格式
ClickHouse MergeTree Part 目录结构:columns.txt、checksums.txt、.bin、.mrk2、primary.idx 语义,Granule 与 Mark 的定位作用,Wide/Compact 布局与 MergeTreeDataPart 源码入口。版本锚定 24.x LTS。
【列存引擎内核】压缩与编码
ClickHouse 列压缩:LZ4、ZSTD、Delta、DoubleDelta、Gorilla 时序编码与列类型关系;CODEC 链顺序、LowCardinality 与 PG TOAST 对照。压缩比须本机实测,本文不编造倍数。
【列存引擎内核】查询读取路径
MergeTree SELECT 读路径:Mark Range 定位 Granule、PREWHERE 与 WHERE、Part 级并行与 max_threads。EXPLAIN indexes=1 解读方法。24.x LTS,无伪造 EXPLAIN 输出。