读优化列存解决的是
扫描少量列、聚合大量行 的分析负载——与 PostgreSQL 行存
OLTP、LSM 写优化
构成存储三角。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 / 日志分析 |
三者不是「谁更好」,而是 工作集与访问模式 不同:
- OLTP 常读整行几列 → 行存一次页内 tuple 命中即可(见 PG 页面与 Tuple)。
- 日志 ingest 每秒百万行 append → LSM 把随机写变成顺序 flush + 后台 compaction(见 LSM Compaction)。
- 分析查询
WHERE date BETWEEN ... GROUP BY region只碰 2–3 列 → 列存只读对应.bin,带宽与解压量按列数缩放。
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:
- 数据按 排序键 稀疏索引(每 granule 一条,默认最多 8192 行),不是每行 B-Tree 项。
- 单行 UPDATE/DELETE 走 mutation 重写 Part,成本高(第 6 篇展开)。
因此 ClickHouse 文档明确建议:高并发小事务 OLTP 不是主场景。
2.3 CPU cache 与顺序访问
列文件内同类型数据连续存放,扫描单列时:
- 解压后的列向量在内存中连续(
PODArray)。 - 预取器对顺序读友好,L1/L2 miss 低于「行存跳列」模式。
MonetDB/X100 论文(CIDR 2005)强调 vectorized execution 与 cache-conscious layout 共同决定 scan 吞吐——ClickHouse 官方 Architecture 文档同样将「按列存储 + 按数组运算」列为核心设计。
flowchart LR
DISK[列.bin 压缩块] --> DEC[解压列向量]
DEC --> CACHE[CPU L1/L2 顺序扫描]
CACHE --> SIMD[SIMD 批量 filter/agg]
三、压缩三角:同质数据 → 高压缩率
行内一行可能含
INT、VARCHAR、JSONB——熵源混合,通用压缩(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):
- 数据在算子间以 Block 传递,默认
thousands 行一批(
max_block_size,常见 65536)。 - 表达式与 filter 对整列数组操作,编译器可 autovec 或手写
SIMD(
src/Columns/、src/Functions/)。
| 维度 | 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 篇),只建立 与存储的接口:
- 解析与规划:SQL → AST → QueryPlan(含 PREWHERE 下推等)。
- 构建 Pipeline:Scan → Filter → Project
→ Aggregate → …,节点为
IProcessor。 - MergeTree Scan:对每张表选 Mark
Range(
primary.idx+ 谓词),并行读多个 Part。 - 列 IO:按 Mark 定位
.bin压缩块 → 解压 → 填入ColumnVector。 - 向量化计算:后续算子在 Block 上批量执行。
- 结果返回:Block 序列化到客户端。
插入路径(对照 LSM):
- Insert block → 内存排序(按 ORDER BY)→ 写新 Part 目录(非原地更新)。
- 与 LSM memtable flush 类似:写放大在 merge,读路径见多个 Part(第 6 篇与 LSM compaction 对照)。
八、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);- ORDER BY:Part 内物理排序,决定 merge 归并顺序。
- PRIMARY KEY:稀疏索引表达式,必须是 ORDER BY 前缀;不保证唯一(与 PG UNIQUE + B-Tree 不同)。
- PARTITION BY:目录级分区,利于 TTL 与 partition pruning。
第 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
Compact(min_bytes_for_wide_part 等
settings,官方 MergeTree settings):
- Wide:每列
.bin+.mrk2,适合宽表只读部分列。 - Compact:小 Part 多列共文件,减少文件数,merge 后常转 Wide。
十、源码导航: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 是常见架构(可观测性系列):
- 高 ingest:批量 insert、合理
PARTITION BY(按天/小时)。 - 查询模式:按
service、status、时间范围聚合——典型少列 scan。 - 运维坑:小 batch 导致 parts 过多、merge 延迟(第 15 篇规划)。
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.metrics、system.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 明确不做什么
官方与工程实践共识(非「缺点列表」,是 边界):
- 无完整 OLTP 事务:多行原子更新、外键约束不是主设计目标。
- UPDATE/DELETE 昂贵:mutation 异步、重写 Part。
- 最终一致语义:ReplacingMergeTree 去重在
merge 后;
FINAL有成本。 - 小 insert 伤 merge:需 batch 或 Buffer 表。
- 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 读者需注意什么?
- Mark 文件
.mrk2/ adaptive granularity 为现代默认。 - Keeper 替代 ZooKeeper 成为副本协调推荐路径(第 8 篇)。
- 具体 setting 默认值以
system.merge_tree_settings为准。
二十一、小结
列存 OLAP 的优势来自 三角:少读列(带宽)→ 同质压缩(磁盘)→ 列向量批量算(CPU)。ClickHouse 用 MergeTree Part + 后台 merge 实现不可变列文件,用 Processors pipeline 实现向量化执行;PRIMARY KEY 是 稀疏排序索引 而非 OLTP 唯一约束。
下一篇进入磁盘:Part 目录里每个文件干什么。
上一篇:系列索引
附录、扩展阅读与工程注记
index_granularity
两个 Mark 之间最大行数;影响稀疏索引粒度与单次读 granule 行数上限。
官方文档默认值(24.x,以实例
system.merge_tree_settings
为准):8192。
index_granularity_bytes
自适应 granule:限制 granule 预估字节大小,宽行表避免单 granule 过大。
官方文档默认值(24.x,以实例
system.merge_tree_settings
为准):10485760 (10 MiB)。
enable_mixed_granularity_parts
是否启用 index_granularity_bytes 与 index_granularity 混合控制。
官方文档默认值(24.x,以实例
system.merge_tree_settings
为准):1。
min_bytes_for_wide_part
Part 体积超过阈值时使用 Wide 布局(列独立文件)。
官方文档默认值(24.x,以实例
system.merge_tree_settings
为准):10485760。
min_rows_for_wide_part
行数超过阈值转 Wide。
官方文档默认值(24.x,以实例
system.merge_tree_settings
为准):0。
parts_to_throw_insert
活跃 Part 数超过阈值拒绝 insert。
官方文档默认值(24.x,以实例
system.merge_tree_settings
为准):3000。
parts_to_delay_insert
超过阈值开始延迟 insert。
官方文档默认值(24.x,以实例
system.merge_tree_settings
为准):1000。
merge_max_block_size
单次 merge 输出块大小上限。
官方文档默认值(24.x,以实例
system.merge_tree_settings
为准):8192。
max_bytes_to_merge_at_max_space_in_pool
merge 池有空闲时单任务最大字节。
官方文档默认值(24.x,以实例
system.merge_tree_settings
为准):161061273600。
number_of_free_entries_in_pool_to_execute_mutation
mutation 与 merge 共享池时的调度参数。
官方文档默认值(24.x,以实例
system.merge_tree_settings
为准):见文档。
compress_marks
是否压缩 Mark 文件。
官方文档默认值(24.x,以实例
system.merge_tree_settings
为准):1。
compress_primary_key
是否压缩 primary.idx 落盘(内存仍解压使用)。
官方文档默认值(24.x,以实例
system.merge_tree_settings
为准):1。
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)。
Part 加载路径
MergeTreeDataPart::loadColumnsChecksumsIndexes()(IMergeTreeDataPart.cpp)依次:
- 读
columns.txt解析列 schema。 - 读
checksums.txt校验各文件。 - 读
count.txt得行数。 - 加载
primary.idx与列 Mark(MergeTreeMarksLoader)。 - 可选加载跳数索引
skp_idx_*。
Part 状态机:Temporary →
PreActive → Active →
Outdated → 删除;merge 产生新 Part 后旧 Part
标记 Outdated。
Wide 格式读列
MergeTreeReaderWide::readRows() 按 Mark
Range 对每个请求列:
- 读
.mrk2得压缩块偏移与 granule 内偏移。 CachedCompressedReadBuffer读.bin压缩块。- 解码器链(
ISerialization+CompressionCodec)还原列片段。
Compact 格式
小 Part 使用 data.compact.bin +
data.compact.mrk3;宽表频繁小 insert
时文件数少,但读单列可能解压同 granule
内其他列——适合窄表或总是读全列的 projection。
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 类型相关。
参考资料
- ClickHouse Documentation, MergeTree table engine, clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree
- ClickHouse Documentation, Architecture Overview, clickhouse.com/docs/en/development/architecture
- ClickHouse Source, v24.3,
src/Storages/MergeTree/,src/Processors/,src/Columns/ - Boncz, Zukowski & Nes, MonetDB/X100: Hyper-Pipelining Query Execution, CIDR 2005
- Abadi et al., The Design and Implementation of Modern Column-Oriented Database Systems, FnT DB 2013
- Stonebraker et al., C-Store: A Column-oriented DBMS, VLDB 2005
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【列存引擎内核】ClickHouse 与 DuckDB 源码级拆解
主选 ClickHouse 拆解 MergeTree 存储格式、向量化执行与分布式协调;DuckDB 作为嵌入式 OLAP 对照。覆盖列存文件布局、merge 机制、跳数索引与生产故障模式,面向数据平台工程师与从 PG/MySQL 转 OLAP 的 DBA。
【列存引擎内核】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 对照。压缩比须本机实测,本文不编造倍数。
【列存引擎内核】向量化执行引擎
ClickHouse Block 列向量 batch、IProcessor Pipeline 与 filter/project/aggregate 向量实现;对照 PostgreSQL 火山模型 ExecProcNode。源码入口 src/Processors、src/Columns。24.x LTS。