土法炼钢兴趣小组的算法知识备份

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

文章导航

分类入口
databasestorage
标签入口
#clickhouse#mergetree#part#granule#mark#column-files#wide#compact#24-lts

目录

MergeTree 的数据单元是 Part——一次 insert block 或一次 merge 输出的 不可变目录。查询、merge、副本同步、mutation 都围绕 Part 展开。搞清 columns.txt.bin.mrk2primary.idx 各干什么,是读 system.parts、诊断 parts 过多、理解 PREWHERE 剪枝的前提。

本文对照 ClickHouse 24.x 官方文档 MergeTree table engine 与源码 src/Storages/MergeTree/IMergeTreeDataPart*本环境未安装 ClickHouse,实验步骤可复现,正文不粘贴未执行的命令输出。


一、Part 在表生命周期中的位置

flowchart LR
  INS[INSERT block] --> SORT[内存按 ORDER BY 排序]
  SORT --> NEW[新 Part 目录]
  NEW --> ACTIVE[Active Parts 可读]
  ACTIVE --> MERGE[Background Merge]
  MERGE --> BIGGER[更大 Part]
  BIGGER --> OUT[旧 Part Outdated 删除]
概念 含义
Partition PARTITION BY 表达式决定的逻辑分桶,对应 data/db/table/ 下子目录或前缀
Part 分区内一段连续排序数据,目录名含块号与 level,如 202406_1_1_0
Block 内存 insert 缓冲,落盘成 Part

同一分区可同时存在 多个 Active Part——读查询合并多 Part 结果(类似 LSM 多层 SST,见 LSM 概览)。


二、Part 目录结构(Wide 格式)

典型 Wide Part(列独立文件),路径形如:

/var/lib/clickhouse/data/<db>/<table>/202406_1_1_0/
├── checksums.txt
├── columns.txt
├── count.txt
├── primary.idx
├── event_date.bin
├── event_date.mrk2
├── user_id.bin
├── user_id.mrk2
├── value.bin
├── value.mrk2
├── minmax_event_date.idx      # 若定义 PARTITION BY
└── skp_idx_<name>.idx / .mrk2 # 若定义跳数索引
flowchart TB
  PART[Part 目录]
  PART --> META[columns.txt / count.txt / checksums.txt]
  PART --> PK[primary.idx 稀疏主键]
  PART --> COL1[event_date.bin + .mrk2]
  PART --> COL2[user_id.bin + .mrk2]
  PK --> G1[Granule 0]
  PK --> G2[Granule 1]
  COL1 --> G1
  COL1 --> G2

2.1 columns.txt

文本格式列清单:名称、类型、默认表达式、编解码器。Part 创建时冻结 schema;ALTER ADD COLUMN 产生新 Part 或 mutation,旧 Part 无新列(读时补默认值)。

2.2 checksums.txt

各文件大小与 hash(如 CityHash128)。副本 fetch、启动加载时校验完整性。

2.3 count.txt

Part 总行数,\(O(1)\) 元数据;与 granule 数、Mark 数一致推导关系见 §四。


三、Granule:最小读索引单元

官方定义:Granule 是 ClickHouse SELECT 时不进一步拆分的最小数据集;每个 granule 含整数行,首行 PK 值写入索引。

控制参数(MergeTree settings):

Setting 作用
index_granularity Mark 间最大行数,默认 8192
index_granularity_bytes 自适应 granule 字节上限,默认 10 MiB
enable_mixed_granularity_parts 启用混合粒度

行宽极大时,单 granule 可能 少于 8192 行甚至 1 行(单行大于 index_granularity_bytes)。

granule 数估算(理想均匀):

\[ G pprox \left\lceil rac{ ext{rows}}{ ext{index\_granularity}} ight ceil \]

自适应粒度下用实际 Mark 数为准(system.parts.primary_key_bytes_in_memory 等)。


四、Mark 文件:.mrk / .mrk2 / .mrk3

Mark 把 逻辑 granule 映射到 .bin 字节位置

扩展名 布局 每 Mark 内容
.mrk 旧固定粒度 Wide 压缩块偏移 + granule 在块内偏移
.mrk2 现代 Wide(自适应) 上述 + granule 行数
.mrk3 Compact Part 指向 data.compact.bin

读路径:primary.idx 定 granule 下标 → 各列 .mrk2 同下标 → 读 .bin 压缩块 → 解压 → 取 granule 行范围。

sequenceDiagram
  participant Q as Query
  participant PK as primary.idx
  participant M as column.mrk2
  participant B as column.bin
  Q->>PK: PK 谓词 → granule range
  Q->>M: 下标 g_lo..g_hi
  M->>B: 压缩块偏移
  B->>Q: 解压列向量

源码:MergeTreeMarksLoader.cppMergeTreeMarkType.h


五、.bin 列数据与压缩块

每个 .bin多个压缩块 串联;一个压缩块可含 多个 granule 的列数据(列存按列压缩,块边界与 granule 边界不对齐)。

流程:

  1. Mark 指向压缩块起始偏移。
  2. 读整块压缩数据,解压到内存 buffer。
  3. 按 granule 内偏移切分列向量片段。
  4. 编解码器链(CODEC(Gorilla, ZSTD) 等)逆序应用。

这与 PG 页内 tuple 完全不同:无「页 = 多列行混合」,而是 列方向压缩块


六、稀疏主键 primary.idx

primary.idx每个 granule 第一条行的 PRIMARY KEY 列值(扁平数组,按 granule 顺序)。数据按 ORDER BY 物理排序,故 PK 单调,可用二分定位谓词范围。

不是

自 23.5+ 起 primary.idx 可压缩落盘(compress_primary_key),加载时解压进内存;仍保持「小 enough 常驻内存」设计目标(官方 MergeTree 文档 Primary Keys and Indexes)。


七、分区元数据

PARTITION BY toYYYYMM(d) 时常见:

目录层级:detached/ 存放 DETACH 的 Part;mutation 产生带 mut 后缀的中间态 Part。


八、Wide vs Compact

Wide Compact
列文件 每列 .bin + .mrk2 data.compact.bin + .mrk3
触发 Part 较大(默认 ≥10 MiB) 小 Part
读少列 只读目标列文件 可能解压 granule 内多列
merge 后 常合并为 Wide 小 Part 合并

Settings:min_bytes_for_wide_partmin_rows_for_wide_partMergeTree settings)。


九、Part 命名与 level

目录名模式:{partition_id}_{min_block}_{max_block}_{level}

system.parts 字段:namerowsbytes_on_diskleveldata_version 等。


十、源码:IMergeTreeDataPart

符号 文件 职责
IMergeTreeDataPart IMergeTreeDataPart.h Part 抽象:加载、列、索引
MergeTreeDataPartWide MergeTreeDataPartWide.cpp Wide 布局
MergeTreeDataPartCompact MergeTreeDataPartCompact.cpp Compact 布局
MergeTreeDataPartWriter MergeTreeDataPartWriter*.cpp 写 Part
MergeTreeMarksLoader MergeTreeMarksLoader.cpp 加载 Mark
MergeTreeIndexGranularity MergeTreeIndexGranularity.cpp granule 边界

加载流程见附录(loadColumnsChecksumsIndexes)。


十一、实验:clickhouse-local 观察 Part

本环境未安装 ClickHouse。 读者在 24.x 执行:

mkdir -p /tmp/ch-local && cd /tmp/ch-local
clickhouse-local --path ./ch_data --multiquery <<'SQL'
CREATE TABLE t (
  d Date,
  id UInt64,
  s String,
  v Float64
) ENGINE = MergeTree()
ORDER BY (d, id);

INSERT INTO t
SELECT '2024-06-01', number, concat('s', toString(number)), number / 3.0
FROM numbers(50000);
SQL

查找 Part 路径(因 --path 而异):

find ./ch_data -name 'columns.txt' | head -5
find ./ch_data -name '*.mrk2' | head -5

对照 §二 列表检查文件是否齐全。请在本机执行后自行记录 ls 输出,此处不伪造。

系统表(server 模式):

SELECT name, part_type, rows, bytes_on_disk, primary_key_bytes_in_memory
FROM system.parts
WHERE database = currentDatabase() AND table = 't' AND active;

part_typeWideCompact


十二、与 hex 对照(方法)

若需二进制层验证:

# 仅说明方法,未在本环境执行
xxd -l 256 path/to/column.bin | head
xxd -l 128 path/to/primary.idx | head

primary.idx 按 PK 列类型固定宽度存储;.bin 前字节为压缩块 header(具体格式见 CompressedWriteBufferCompressionCodecFactory 源码)。无实测 hex 时不解读具体字节。


十三、Detached / Broken Parts

副本场景见 第 8 篇


十四、工程坑点

14.1 小 insert → 海量 Part

每个 insert block 可产生新 Part;秒级 thousands insert → merge 跟不上 → parts_to_delay_insert / parts_to_throw_insert(第 6、15 篇)。

14.2 宽表 + Compact

频繁小 batch 宽表可能长期 Compact,读单列仍解压整 granule——调 min_bytes_for_wide_part 或增大 batch。

14.3 跳数索引文件

skp_idx_* 与列 Mark 对齐 granule;定义不当几乎无剪枝(第 7 篇)。


十五、小结

Part = 不可变列文件集合 + 元数据 + 稀疏索引;Granule = 索引与 IO 对齐单位;Mark = granule 到 .bin 的桥梁。读路径永远:PK → granule range → Mark → 压缩块 → 列向量


上一篇列存基础

下一篇压缩与编码


附录、扩展阅读与工程注记

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

Part 加载路径

MergeTreeDataPart::loadColumnsChecksumsIndexes()IMergeTreeDataPart.cpp)依次:

  1. columns.txt 解析列 schema。
  2. checksums.txt 校验各文件。
  3. count.txt 得行数。
  4. 加载 primary.idx 与列 Mark(MergeTreeMarksLoader)。
  5. 可选加载跳数索引 skp_idx_*

Part 状态机:TemporaryPreActiveActiveOutdated → 删除;merge 产生新 Part 后旧 Part 标记 Outdated。

Wide 格式读列

MergeTreeReaderWide::readRows() 按 Mark Range 对每个请求列:

Compact 格式

小 Part 使用 data.compact.bin + data.compact.mrk3;宽表频繁小 insert 时文件数少,但读单列可能解压同 granule 内其他列——适合窄表或总是读全列的 projection。

编解码器链顺序

DDL 示例:value Float64 CODEC(Gorilla, ZSTD) 表示 先 Gorilla 专用编码,再 ZSTD 通用压缩。读路径逆序解压。

内置通用压缩:NONELZ4ZSTD(及 level 变体)。专用编码见官方 Compression codecs 表。

Delta 族数学

对序列 \(x_1,\ldots,x_n\),Delta 存 \(d_i = x_i - x_{i-1}\)(首元素规则见实现)。DoubleDelta 对 \(d_i\) 再差分,适合时间戳等二阶平滑序列。

Gorilla 参考

Pelkonen et al., Gorilla: A Fast, Scalable, In-Memory Time Series Database, VLDB 2015。XOR 压缩相邻浮点值的 leading/trailing zero 相同前缀。

LowCardinality

内部维护字典 + 索引列;对低基数 String/Enum 减少存储与 group by 字典优化。与 Plain 编码选择属于 DDL 设计(非自动 TOAST)。

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_bytesdata_uncompressed_bytescompression_codec——压缩实验应用此表而非猜。见第 3 篇 benchmark 框架。

Mark Cache 与 Primary Index Cache

频繁查询受益 mark_cacheprimary_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_queueGET_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.eventsOSIOWait* vs UserTime 辅助判断,需本机 profile。

max_threads 与 cores

max_threads 默认与 CPU 核相关;过大线程增加 Part 并行读开销与内存。HTAP 混部应限制 CH 查询线程池。

内存跟踪

system.metricsMemoryTrackingMergesMutationsMemoryTracking;OOM 前常见 merge 与 big aggregation 同抢内存。

Part 命名与 mutation

mutation 产生 xxx_mutyyy 中间 Part;完成后旧 Part outdated。system.mutationsparts_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:StorageMergeTreeIMergeTreeDataPartMergeTreeReaderWideMergeTreeDataMergerMutator。执行:PipelineExecutorIProcessor

社区与 LTS

24.x LTS 安全/backport 周期见官网;生产锚定 LTS 而非 latest。

system.parts 字段解读

运维读 Part 状态时常用列:databasetablename(目录名)、part_type(Wide/Compact)、rowsbytes_on_diskbytes_on_disk_uncompressedprimary_key_bytes_in_memorymarks_bytes_on_diskleveldata_versionis_frozenactive=0 表示已被 merge 替换但未物理删除。与 LSM SST 层数 类似,应监控每表 active part 数趋势。

system.merges 与 merge 进度

system.merges 展示当前运行中的 merge:databasetableelapsedprogress(0–1)、num_partsresult_part_nametotal_size_bytes_uncompressed。长时间 progress 不动可能磁盘 IO 饱和或单 Part 过大。merge_max_block_size 影响单次归并行数。

system.query_log 读路径指标

启用 query_log 后,read_rowsread_bytesresult_rows 对比可验证索引剪枝是否生效。PREWHERE 优化通常降低 read_bytesread_rows 仍含 granule 内过滤前行数。本系列不在此环境给出样本数值。

MergeTreeWriteSettings 与 insert

除表级 merge_tree settings 外,insert 受 max_insert_block_sizemin_insert_block_size_rowsasync_insert(24.x 异步 insert 特性以文档为准)影响。小 block 直接对应小 Part——平台侧应强制 batch 或 Buffer 引擎。

StoragePolicy 与多磁盘

TTL MOVE 与 storage_policy 可将 Part 移至慢盘/对象存储。merge 与 fetch 路径需保证目标卷有足够空间;system.diskssystem.storage_policies 描述卷与策略。

Projection 与读优化(边界)

24.x 支持 projection 预聚合/排序副本。属于高级 DDL,本系列主路径不展开;知晓其存在可避免与跳数索引职责混淆——projection 是额外 Part 子集,非跳数索引。

Sample By 与近似查询

SAMPLE BYSAMPLE 子句依赖排序键哈希;与主键剪枝独立。日志采样分析常用,但不替代正确 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_bytesdata_uncompressed_bytescompression_codec——压缩实验应用此表而非猜。见第 3 篇 benchmark 框架。

Mark Cache 与 Primary Index Cache

频繁查询受益 mark_cacheprimary_index_cache(配置与 metric 见文档)。冷查询首次读盘仍取决于 Mark/PK 大小。

ReadInOrder 优化

若查询 ORDER BY 与表排序键一致且无大幅改写,优化器可走 ReadInOrder 减少全量排序内存。与 merge 物理排序强相关。

参考资料

  1. ClickHouse Documentation, MergeTree table engine, Granules and marks
  2. ClickHouse Documentation, MergeTree settings, index_granularity / min_bytes_for_wide_part
  3. ClickHouse Source, v24.3, src/Storages/MergeTree/IMergeTreeDataPart.cpp
  4. ClickHouse Source, MergeTreeDataPartWide.cpp, MergeTreeMarksLoader.cpp ### 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

Part 加载路径

MergeTreeDataPart::loadColumnsChecksumsIndexes()IMergeTreeDataPart.cpp)依次:

  1. columns.txt 解析列 schema。
  2. checksums.txt 校验各文件。
  3. count.txt 得行数。
  4. 加载 primary.idx 与列 Mark(MergeTreeMarksLoader)。
  5. 可选加载跳数索引 skp_idx_*

Part 状态机:TemporaryPreActiveActiveOutdated → 删除;merge 产生新 Part 后旧 Part 标记 Outdated。

Wide 格式读列

MergeTreeReaderWide::readRows() 按 Mark Range 对每个请求列:

Compact 格式

小 Part 使用 data.compact.bin + data.compact.mrk3;宽表频繁小 insert 时文件数少,但读单列可能解压同 granule 内其他列——适合窄表或总是读全列的 projection。

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-06-18 · database / storage

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

行存 vs 列存的带宽、压缩与向量化三角;ClickHouse Server 进程模型、线程池与 MergeTree 引擎家族地图;src/Storages 与 src/Processors 源码入口。对照 PG 行存与 LSM 写优化路径,版本锚定 ClickHouse 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。

2026-06-18 · database / storage

【列存引擎内核】查询读取路径

MergeTree SELECT 读路径:Mark Range 定位 Granule、PREWHERE 与 WHERE、Part 级并行与 max_threads。EXPLAIN indexes=1 解读方法。24.x LTS,无伪造 EXPLAIN 输出。


By .