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

【列存引擎内核】列存基础与 ClickHouse 架构

文章导航

分类入口
databasestorage
标签入口
#clickhouse#columnar#mergetree#olap#vectorization#storage-engine#24-lts

目录

读优化列存解决的是 扫描少量列、聚合大量行 的分析负载——与 PostgreSQL 行存 OLTPLSM 写优化 构成存储三角。ClickHouse 是开源列存工程标杆:MergeTree 家族完整、源码在 src/Storages/MergeTree/ 结构清晰,官方文档对 Part 格式与 merge 机制有详尽说明(ClickHouse Documentation, MergeTree table engine)。

本文建立列存心智模型与 ClickHouse 进程/引擎地图,不写 SQL 教程。读完应能回答:为什么 SELECT sum(price) FROM orders 在列存上比行存省 IO;ClickHouse 单进程里哪些线程在跑 merge;MergeTree 家族各引擎差在哪。

版本锚定:ClickHouse 24.x LTS(下文源码路径以 release-24.3 为参考,24.8 等 LTS 分支结构基本一致)。


一、存储三角:行存、LSM、列存各守哪条边

引擎类型 代表 优化目标 典型负载
行存 B-Tree PostgreSQL 点查、短事务、MVCC OLTP
LSM LevelDB/RocksDB 路径 顺序写、高 ingest 写密集 KV / 日志
列存 MergeTree ClickHouse 列扫描、压缩、向量化聚合 OLAP / 日志分析

三者不是「谁更好」,而是 工作集与访问模式 不同:

flowchart TB
  subgraph triangle [存储引擎三角]
    ROW[行存 OLTP]
    LSM[LSM 写优化]
    COL[列存读优化]
  end
  ROW --> PG[PostgreSQL / InnoDB]
  LSM --> CH_INGEST[高吞吐 ingest 也可走 CH]
  COL --> CH_OLAP[ClickHouse MergeTree]
  PG --> HTAP[HTAP 需双引擎或副本]
  CH_OLAP --> PG_COPY[常作 PG 分析副本]

storage 系列 已讲通用块设备与压缩原理——本系列落到 ClickHouse 文件格式、读路径与 merge


二、行存 vs 列存:带宽模型

2.1 简化 IO 量估算

设表有 \(C\) 列、每列平均 \(w_c\) 字节宽,查询投影 \(k\) 列、需扫描 \(N\) 行。

行存(忽略索引剪枝)从堆表读近似:

\[ B_{\text{row}} \approx N \cdot \sum_{c=1}^{C} w_c = N \cdot W_{\text{row}} \]

列存 只读 \(k\) 列:

\[ B_{\text{col}} \approx N \cdot \sum_{c \in S} w_c,\quad |S| = k \]

\(k \ll C\)\(W_{\text{row}}\) 较大时,\(B_{\text{col}} / B_{\text{row}} \approx k/C\)。宽表(\(C=200\),查 3 列)理论 IO 差两个数量级——这是列存 OLAP 的 第一性优势,不依赖 benchmark 数字,仅来自布局(Abadi et al., 2013, §2)。

2.2 点查与更新:行存主场

点查 WHERE id = ? 需定位一行并通常读多列 → 行存 B-Tree 一次索引 descent + 页内 tuple(PG B-Tree)。

列存 MergeTree:

因此 ClickHouse 文档明确建议:高并发小事务 OLTP 不是主场景

2.3 CPU cache 与顺序访问

列文件内同类型数据连续存放,扫描单列时:

  1. 解压后的列向量在内存中连续(PODArray)。
  2. 预取器对顺序读友好,L1/L2 miss 低于「行存跳列」模式。

MonetDB/X100 论文(CIDR 2005)强调 vectorized executioncache-conscious layout 共同决定 scan 吞吐——ClickHouse 官方 Architecture 文档同样将「按列存储 + 按数组运算」列为核心设计。

flowchart LR
  DISK[列.bin 压缩块] --> DEC[解压列向量]
  DEC --> CACHE[CPU L1/L2 顺序扫描]
  CACHE --> SIMD[SIMD 批量 filter/agg]

三、压缩三角:同质数据 → 高压缩率

行内一行可能含 INTVARCHARJSONB——熵源混合,通用压缩(PG TOAST 的 pglz/lz4)单块压缩率有限(PG TOAST)。

列存 同列同质

列类型 编码思路
单调 DateTime DoubleDelta 第 3 篇
缓慢变化 Float64 指标 Gorilla XOR 第 3 篇
低基数 String LowCardinality 字典 + LZ4/ZSTD 第 3 篇
高基数 UUID 通常仅 ZSTD,压缩率低 第 3 篇

压缩在 insert / merge 写路径 完成;读路径先解压 granule 再向量化——压缩率与解压 CPU 是显式权衡(LZ4 偏速度,ZSTD 偏率,官方 Compression codecs)。


四、向量化:从 Volcano 到 Column Batch

经典 Volcano 模型PG 执行器):ExecProcNode() 每次返回 一行,父算子循环调用,函数调用与虚 dispatch 开销大。

ClickHouse 向量化执行(官方 Architecture Overview):

维度 PG 火山 ClickHouse 向量
数据单元 TupleTableSlot 一行 Block 多列向量
Filter 逐行 EEO 位图 / 整列比较
Aggregate 逐行 transition 批量 update 状态
适用 通用 OLTP 分析 scan-heavy

第 4 篇展开 IProcessor pipeline;此处只需建立:列存优势 = 少读盘 + 解压后批量算


五、ClickHouse 产品形态与部署

5.1 单进程多线程 Server

典型部署:clickhouse-server 单进程(也可容器化),客户端 clickhouse-client / HTTP / JDBC。

与 DuckDB 嵌入式 in-process 对比(第 11 篇规划):ClickHouse 偏 独立服务、高并发分析

5.2 数据目录

默认 /var/lib/clickhouse/,结构概览:

/var/lib/clickhouse/
├── data/<database>/<table>/     # MergeTree parts
├── metadata/                    # 表 DDL
├── store/                       # 24.x 部分路径调整,以实际为准
└── flags/                       # 运维标志

具体 Part 目录见 第 2 篇

5.3 版本与 LTS

24.x LTS 分支(如 24.3、24.8)适合生产锚定。Breaking 变更查 官方 changelog;本系列引用的 merge tree settings 以 24.3+ 文档为准。


六、进程内组件:谁在处理查询与 merge

flowchart TB
  CLIENT[Client TCP/HTTP] --> SERVER[clickhouse-server]
  SERVER --> PARSER[Parser / Analyzer]
  PARSER --> PLAN[QueryPlan / Planner]
  PLAN --> PIPE[Pipeline Processors]
  PIPE --> READ[MergeTreeReadPool]
  READ --> PART[Part .bin / .mrk]
  SERVER --> BG[BackgroundSchedulePool]
  BG --> MERGE[MergeTreeBackgroundExecutor]
  BG --> MUT[Mutations]
  BG --> FETCH[Replicated fetch]
组件 职责 源码线索
Server 连接、查询协调、DDL programs/server/
Pipeline 向量化算子 DAG src/Processors/
MergeTreeReadPool Part/ granule 并行读 src/Storages/MergeTree/
BackgroundSchedulePool merge、mutation、TTL MergeTreeBackgroundExecutor
系统表 可观测性 system.parts, system.merges

官方文档:Architecture Overview 指出查询尽可能在 数组(列向量) 上 dispatch,而非标量循环。


七、一次 SELECT 的粗粒度路径

不展开优化器细节(第 5 篇),只建立 与存储的接口

  1. 解析与规划:SQL → AST → QueryPlan(含 PREWHERE 下推等)。
  2. 构建 Pipeline:Scan → Filter → Project → Aggregate → …,节点为 IProcessor
  3. MergeTree Scan:对每张表选 Mark Rangeprimary.idx + 谓词),并行读多个 Part。
  4. 列 IO:按 Mark 定位 .bin 压缩块 → 解压 → 填入 ColumnVector
  5. 向量化计算:后续算子在 Block 上批量执行。
  6. 结果返回:Block 序列化到客户端。

插入路径(对照 LSM):


八、MergeTree 引擎家族地图

MergeTree 是 默认分析引擎;家族成员在 merge 时附加语义:

引擎 merge 时行为 典型场景
MergeTree 按排序键归并 Part 通用事实表
ReplacingMergeTree 同排序键保留 version 最大行 去重维度 / CDC 终态
SummingMergeTree 同键数值列求和 预聚合指标
AggregatingMergeTree 同键合并 aggregate state 物化聚合
CollapsingMergeTree sign 列折叠 +/- 行 变更日志折叠
VersionedCollapsingMergeTree 带 version 的折叠 复杂变更流
GraphiteMergeTree 类 Graphite rollup 监控降精度

重要:Replacing/Summing 等的语义多在 merge 后 才完全体现;查询层可用 FINAL 强制 merge 语义,但有代价(官方文档 ReplacingMergeTree 警告)。

flowchart LR
  INSERT[Insert Part] --> PARTS[多个 Part 并存]
  PARTS --> MERGE[Background Merge]
  MERGE --> BIG[更大 Part]
  MERGE --> REPL[Replacing 去重]
  MERGE --> SUM[Summing 求和]

8.1 PRIMARY KEY 与 ORDER BY

DDL 常见:

CREATE TABLE t (
  event_date Date,
  user_id UInt64,
  ...
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
PRIMARY KEY (event_date, user_id);

第 7 篇展开 primary.idx 与跳数索引。

8.2 其他常用表引擎(边界)

引擎 角色
Log/TinyLog 无 merge,测试/临时
Memory 纯内存
Buffer 缓冲小 insert 再刷 MergeTree
Distributed 分片路由(第 9 篇)
ReplicatedMergeTree 副本(第 8 篇)
Kafka / S3Queue 外部队列 ingest

本系列 主战场是 MergeTree 家族;Log/Memory 不展开。


九、源码导航:src/Storages/

ClickHouse 源码(GitHub ClickHouse/ClickHouse,tag v24.3.x)中与存储相关的入口:

路径 内容
src/Storages/IStorage.h 表引擎抽象:read/write/alter
src/Storages/StorageMergeTree.cpp 本地 MergeTree 表
src/Storages/StorageReplicatedMergeTree.cpp 副本表
src/Storages/MergeTree/ Part、merge、mutation、索引
src/Storages/MergeTree/IMergeTreeDataPart.h Part 元数据与生命周期
src/Storages/MergeTree/MergeTreeDataPartWide.cpp Wide 格式(列独立文件)
src/Storages/MergeTree/MergeTreeDataPartCompact.cpp Compact 格式
src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp merge/mutation 执行
src/Storages/MergeTree/MergeTreeBackgroundExecutor.cpp 后台任务调度

读 Part 时关注 Wide vs Compactmin_bytes_for_wide_part 等 settings,官方 MergeTree settings):


十、源码导航:src/Processors/src/Columns/

路径 内容
src/Processors/IProcessor.h 算子接口:prepare/work
src/Processors/Executors/PipelineExecutor.cpp 多线程跑 pipeline
src/Processors/QueryPlan/ 逻辑计划到物理算子
src/Processors/Transforms/ Filter/Aggregating 等
src/Columns/IColumn.h 列抽象
src/Columns/ColumnVector.h 数值列向量
src/Columns/ColumnString.h 字符串列
src/Interpreters/ExpressionActions.cpp 表达式编译到列操作

Block 定义在 src/Core/Block.h:列名 + ColumnWithTypeAndName 数组,是 向量化数据载体


十一、与 PostgreSQL 的对照心智

话题 PostgreSQL ClickHouse MergeTree
主键 唯一 + 索引 排序键前缀,稀疏,不唯一
更新 原地 tuple + WAL mutation 重写 Part
事务 ACID MVCC 无跨 Part 多行事务语义
分析 顺序扫 + 并行 列剪枝 + 向量 + merge
副本 流复制 ReplicatedMergeTree + Keeper

常见架构:PG 作 OLTP 源,ClickHouse 作 分析副本(MaterializedPostgreSQL / 外部 ETL)——见 PG 监控可观测性 写入场景。


十二、与 LSM 的对照:Part vs SSTable

LSM SSTable MergeTree Part
不可变单元 SST 文件 Part 目录
后台整理 Compaction 多层 Merge 同分区 Part
读放大 多层 + Bloom 多 Part + 稀疏索引
写路径 WAL + MemTable Insert block → Part
删除 Tombstone Mutation / TTL merge

LSM 第 4 篇 的多路归并与 MergeTree merge 思想同源:不可变文件 + 后台归并,换写吞吐与读整理成本。


十三、可观测性与日志分析场景

日志/trace/metrics 写入 ClickHouse 是常见架构(可观测性系列):

MergeTree 不是唯一选择(Elastic、Loki 等),但在 SQL + 列存压缩 + 集群扩展 组合下工程量大。


十四、线程、内存与资源隔离(概览)

Server 配置(config.xml + users/settings)中与分析相关的默认值需结合 workload 调:

Setting 含义
max_threads 查询 CPU 线程上限
max_memory_usage 单查询内存
max_insert_block_size insert 块大小
background_pool_size merge 等后台线程

内存跟踪:system.metricssystem.asynchronous_metrics(第 14 篇规划)。OOM 常来自大 GROUP BY 或过多 Part 同时读——不是「列存不占内存」。


十五、实验环境:本机未安装 ClickHouse

本写作环境未安装 ClickHouse——下文不给任何伪造的 clickhouse-client 输出。读者可在 24.x LTS 上复现:

# Ubuntu/Debian 示例(以官方安装文档为准)
sudo apt-get install -y apt-transport-https ca-certificates curl
curl -fsSL 'https://packages.clickhouse.com/rpm/lts/repodata/repomd.xml.key' | sudo gpg --dearmor -o /usr/share/keyrings/clickhouse-keyring.gpg
# 或 Docker:
docker run -d --name ch24 -p 8123:8123 clickhouse/clickhouse-server:24.3

clickhouse-client --query "SELECT version()"

验证架构理解的最小步骤:

CREATE TABLE demo.events (
  d Date,
  id UInt64,
  v Float64
) ENGINE = MergeTree()
ORDER BY (d, id);

INSERT INTO demo.events SELECT today(), number, rand() / 1e9 FROM numbers(100000);

SELECT count(), sum(v) FROM demo.events WHERE d = today();
SELECT name, rows, bytes_on_disk FROM system.parts WHERE table = 'events';

Part 目录结构与 第 2 篇 对照。


十六、DuckDB 与嵌入式 OLAP(预告)

DuckDB 同列存 + 向量化,但 in-process、无独立 Server(第 11 篇规划)。对照:

ClickHouse DuckDB
部署 服务
并发 多客户端 单进程内
存储 MergeTree Part Row Group + Segment
联邦 PostgreSQL 表函数等 pg_duckdb

第 13 篇做选型决策树,不在此排名。


十七、设计取舍:ClickHouse 明确不做什么

官方与工程实践共识(非「缺点列表」,是 边界):

  1. 无完整 OLTP 事务:多行原子更新、外键约束不是主设计目标。
  2. UPDATE/DELETE 昂贵:mutation 异步、重写 Part。
  3. 最终一致语义:ReplacingMergeTree 去重在 merge 后;FINAL 有成本。
  4. 小 insert 伤 merge:需 batch 或 Buffer 表。
  5. Cloud 托管版内部实现:本系列不覆盖 ClickHouse Cloud 控制面。

十八、阅读路线与系列依赖

flowchart TD
  A[01 本文 架构] --> B[02 Part 格式]
  B --> C[03 压缩编码]
  C --> D[04 向量化]
  D --> E[05 读路径]
  B --> F[06 Merge Mutation]
  F --> G[07 索引]
  G --> H[08 副本]
读者背景 建议
从 PG 来 本文 → 07 索引 → 05 读路径
从 LSM 来 本文 §十二 → 06 merge
OLAP 开发 01 → 02 → 04 → 05
平台运维 01 §六 → 06 → 08 → 14–16

十九、术语表

术语 含义
Part MergeTree 不可变数据目录,一次 insert block 或 merge 产物
Granule Part 内最小索引/读单元,行数 \(\in [1, \text{index\_granularity}]\)
Mark granule 在列 .bin 中的偏移信息(.mrk2
Block 内存中列向量 batch,算子间传递单位
Merge 后台归并多个 Part 为更大 Part
Mutation 异步 ALTER UPDATE/DELETE
Sparse index primary.idx,每 granule 一条键值

二十、常见问题

20.1 ClickHouse 是列存还是行存?

列存:磁盘上每列独立文件(Wide Part);内存与执行以列向量为主。Compact Part 仅在 小 Part 阶段多列打包,merge 后常转 Wide。

20.2 为什么没有 B-Tree 主键?

海量 insert 下每行维护 B-Tree 更新成本过高;稀疏索引 + 排序数据 + mark 定位 granule 是 写吞吐与读剪枝的折中(官方 MergeTree 文档 Granules 段)。

20.3 与 Parquet/ORC 文件格式关系?

ClickHouse 可读 Parquet,但原生 MergeTree Part 与 merge / 副本 / 突变 深度集成;Parquet 作外表引擎,不走完整 MergeTree 生命周期。

20.4 24.x 相对 23.x 读者需注意什么?


二十一、学术谱系:C-Store · MonetDB/X100 · ClickHouse

读完存储三角与进程模型后,应能回答:今天的 MergeTree 从哪两篇 2005 年论文分叉,又在哪一点上故意不跟论文走

阶段 系统 / 文献 相对前一代
早期 DSM / 列式存储直觉(Copeland 等) 同列连续存放,利于 scan
2005 Stonebraker et al., C-Store, VLDB Projection(列组)、写存储 WS / 读存储 RS、排序键与冗余投影
2005 Boncz et al., MonetDB/X100, CIDR Vectorized + cache-conscious;批处理打满 CPU 流水线
2008 Abadi et al., Column-Stores vs Row-Stores, SIGMOD 证明「只把行存改成按列存盘」不够;需 late materialization、压缩、向量算子等 系统栈
2010s+ ClickHouse MergeTree 不可变 Part + 后台 merge;Block/IProcessor 向量化(本系列第 2、4 篇)

谱系结论:C-Store 定义 列存架构问题(投影、读写分离、排序);X100 定义 执行问题(为何必须向量批);Abadi 2008 钉死 工程清单。ClickHouse 用 Part/merge 实现「类 RS 的不可变列文件」,用 Processors 实现 X100 式 batch——不是 C-Store 源码移植,也 不是 MonetDB 方言兼容层。

21.1 争论:只改存储布局够不够?

ClickHouse 官方 Architecture 把「按列存储 + 按数组运算」并列——与 B 派一致。本系列第 4–5 篇展开 Block 与 PREWHERE;此处只需建立:列存三角缺一角,论文与生产都会打回「假列存」

21.2 工程间隙

论文常见设定 ClickHouse 24.x 生产
单机内存 / TPC-H 风格 scan 多租户、持续 ingest、parts 与 merge 争盘
C-Store WS/RS 显式双存储 单一 MergeTree 家族:insert 追加 Part,merge 归并(无独立 WS 引擎名)
Projection 由优化器选冗余副本 CH PROJECTION 存在但非主路径;主路径靠 ORDER BY 排序键 + 跳数索引(第 7 篇)
实验少谈副本协调 ReplicatedMergeTree + Keeper(第 8 篇)

21.3 开放问题

  1. Projection 是否应成为默认物化路径? C-Store 以多投影换查询;CH 多数集群靠排序键 + MV。可检验:同一宽表加 projection 前后 EXPLAIN indexes 与 parts 体积(入口:C-Store §3;CH 文档 Projections)。
  2. 向量化与 codegen 谁主导下一代 CH? 见第 4 篇 Kersten VLDB 2018 争论;开放点是 JIT 在多租户短查询上的编译摊销。
  3. 湖仓列式文件是否取代服务端 MergeTree? Parquet/Iceberg 路径见 lakehouse;本系列不假装已统一。

二十二、小结

列存 OLAP 的优势来自 三角:少读列(带宽)→ 同质压缩(磁盘)→ 列向量批量算(CPU)。学术坐标上,三角分别对应 C-Store / 压缩编码文献 / MonetDB/X100;ClickHouse 用 MergeTree Part + 后台 mergeProcessors pipeline 落地。PRIMARY KEY 是 稀疏排序索引 而非 OLTP 唯一约束。

下一篇进入磁盘:Part 目录里每个文件干什么


上一篇系列索引

下一篇MergeTree Part 文件格式


参考资料

核心论文

  1. Stonebraker et al., C-Store: A Column-oriented DBMS, VLDB 2005(projection、WS/RS;A 级)。
  2. Boncz, Zukowski & Nes, MonetDB/X100: Hyper-Pipelining Query Execution, CIDR 2005(向量化奠基;A 级)。
  3. Abadi, Madden & Hachem, Column-Stores vs. Row-Stores: How Different Are They Really?, SIGMOD 2008(系统栈清单;A 级)。
  4. Abadi et al., The Design and Implementation of Modern Column-Oriented Database Systems, Foundations and Trends in Databases 2013(综述;A 级)。
  5. Graefe, Volcano—An Extensible and Parallel Query Evaluation System, IEEE TKDE 1994(迭代器基线;A 级)。

规范 / 源码 / 文档

  1. ClickHouse Documentation, MergeTree table engine / Architecture Overview(24.x;A 级)。
  2. ClickHouse Source, v24.3, src/Storages/MergeTree/src/Processors/src/Columns/(A 级)。

读完这篇,下一步读什么

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

2026-06-18 · database / storage

【列存引擎内核】ClickHouse 与 DuckDB 源码级拆解

主选 ClickHouse 拆解 MergeTree 存储格式、向量化执行与分布式协调;DuckDB 作为嵌入式 OLAP 对照。覆盖列存文件布局、merge 机制、跳数索引与生产故障模式,面向数据平台工程师与从 PG/MySQL 转 OLAP 的 DBA。

2026-06-18 · database / storage

【列存引擎内核】MergeTree Part 文件格式

ClickHouse MergeTree Part 目录结构:columns.txt、checksums.txt、.bin、.mrk2、primary.idx 语义,Granule 与 Mark 的定位作用,Wide/Compact 布局与 MergeTreeDataPart 源码入口。版本锚定 24.x LTS。

2026-06-18 · database / storage

【列存引擎内核】压缩与编码

ClickHouse 列压缩:LZ4、ZSTD、Delta、DoubleDelta、Gorilla 时序编码与列类型关系;CODEC 链顺序、LowCardinality 与 PG TOAST 对照。压缩比须本机实测,本文不编造倍数。

2026-06-18 · database / storage

【列存引擎内核】向量化执行引擎

ClickHouse Block 列向量 batch、IProcessor Pipeline 与 filter/project/aggregate 向量实现;对照 PostgreSQL 火山模型 ExecProcNode。源码入口 src/Processors、src/Columns。24.x LTS。


By .