跳转到主要内容

ClickHouse 中的 part 合并是什么?


ClickHouse 速度很快,不仅查询快,插入也快,这要归功于其存储层。它的工作方式与 LSM trees 类似: ① 插入到 MergeTree 引擎家族的表中时,会创建经过排序且不可变的数据分区片段 ② 所有数据处理都会交由后台 part 合并完成。 这使得数据写入开销很低,而且效率极高 为了控制每个表的分片数量并实现上面的 ②,ClickHouse 会在后台持续将较小的分片 (按分区) 合并为较大的分片,直到其 compressed size 达到约 ~150 GB 下图概述了这一后台合并过程:
分片的 merge level 每经历一次额外的合并就会加一。level 为 0 表示该分片是新的,尚未被合并。已合并到更大分片中的分片会被标记为 inactive,并在一段可配置的时间后最终删除 (默认 8 分钟) 。随着时间推移,这会形成一棵由合并后分片构成的。这也正是 merge tree 表名称的由来。

监控合并

什么是表分片示例中,我们说明了 ClickHouse 会在 parts 系统表中跟踪所有表分片。我们使用以下查询来获取示例表中每个活动分片的合并级别以及存储的行数:
SELECT
    name,
    level,
    rows
FROM system.parts
WHERE (database = 'uk') AND (`table` = 'uk_price_paid_simple') AND active
ORDER BY name ASC;
前文所述的查询结果表明,示例表有四个处于活动状态的分片,每个分片都是由最初插入的分片经过一次合并创建而成:
   ┌─name────────┬─level─┬────rows─┐
1. │ all_0_5_1   │     1 │ 6368414 │
2. │ all_12_17_1 │     1 │ 6442494 │
3. │ all_18_23_1 │     1 │ 5977762 │
4. │ all_6_11_1  │     1 │ 6459763 │
   └─────────────┴───────┴─────────┘
运行该查询后,现在会看到这四个分片已合并为一个最终的分片 (只要表中没有进一步的 insert 操作) :
   ┌─name───────┬─level─┬─────rows─┐
1. │ all_0_23_2 │     2 │ 25248433 │
   └────────────┴───────┴──────────┘
在 ClickHouse 24.10 中,内置的监控仪表盘新增了一个合并仪表盘。在 OSS 和 Cloud 中都可通过 /merges HTTP handler 访问,我们可以用它来可视化示例表中的所有 part 合并:
上方录制的仪表盘展示了从最初插入数据到最终合并为单个分片的整个过程: ① 活跃分片的数量。 ② part 合并,以方框形式展示 (大小反映分片的大小) 。 写入放大

并发合并

单个 ClickHouse server 会使用多个后台合并线程并发执行 part 合并:
每个合并线程都会执行以下循环: ① 决定下一步要合并哪些分片,并将这些分片加载到内存中。 ② 在内存中将这些分片合并为一个更大的分片。 ③ 将合并后的分片写入磁盘。 返回 ① 请注意,增加 CPU 核心数量和 RAM 容量可以提升后台合并吞吐量。

内存优化合并

ClickHouse 不一定会像前面的示例中那样,一次性将所有待合并的分片 全部加载到内存中。根据若干因素,为了降低内存消耗 (代价是合并速度变慢) ,所谓的垂直合并会按块分批加载并合并分片,而不是一次性完成。

合并机制

下图说明了 ClickHouse 中单个后台合并线程如何合并分片 (默认情况下,不使用垂直合并) :
part 合并分为以下几个步骤: ① 解压与加载:待合并分片中的压缩二进制列文件会先解压并加载到内存中。 ② 合并:数据会被合并成更大的列文件。 ③ 索引:系统会为合并后的列文件生成新的稀疏主索引 ④ 压缩与存储:新的列文件和索引会被压缩,并保存在一个新的目录中,该目录表示合并后的数据分片。 数据分片中的其他元数据 (如二级数据跳过索引、列统计信息、校验和以及 min-max 索引) 也会基于合并后的列文件重新创建。为简化说明,我们省略了这些细节。 步骤 ② 的具体机制取决于所使用的 MergeTree 引擎,因为不同引擎处理合并的方式不同。例如,行可能会被聚合;如果数据已过期,也可能会被替换。正如前文所述,这种方法会将所有数据处理都交由后台合并完成,从而让写入操作保持轻量高效,实现超高速插入 接下来,我们将简要介绍 MergeTree 家族中几种特定引擎的合并机制。

标准合并

下图说明了标准 MergeTree 表中的分片是如何合并的:
上图中的 DDL 语句创建了一个排序键为 (town, street)MergeTree 表,这意味着 磁盘上的数据会按这些列排序,并据此生成稀疏主索引。 ① 解压后的预排序表列会在保持由表排序键定义的全局排序顺序的同时进行 ② 合并,③ 生成新的稀疏主索引,然后 ④ 将合并后的列文件和索引压缩,并作为新的数据分片存储到磁盘上。

Replacing 合并

ReplacingMergeTree 表中,part 合并的工作方式与标准合并类似,但每一行只会保留最新版本,旧版本会被丢弃:
上图中的 DDL 语句创建了一个 ReplacingMergeTree 表,其排序键为 (town, street, id);这意味着磁盘上的数据会按这些列排序,并据此生成稀疏主索引。 ② 合并的工作方式与标准 MergeTree 表类似:在保持全局排序顺序的同时,合并已解压且预先排序的列。 不过,ReplacingMergeTree 会删除具有相同排序键的重复行,只保留最新的一行;这里的“最新”以其所在数据分片的创建时间戳为准。

求和合并

SummingMergeTree 表的 part 合并期间,数值数据会被自动汇总:
上图中的 DDL 语句定义了一个 SummingMergeTree 表,并将 town 设为排序键,这意味着磁盘上的数据会按该列排序,并据此创建稀疏主索引。 在 ② 合并步骤中,ClickHouse 会将所有具有相同排序键的行替换为一行,并对数值列的值求和。

聚合合并

上面的 SummingMergeTree 表示例是 AggregatingMergeTree 表的一种特化变体,它支持在part 合并期间应用 90+ 种聚合函数中的任意一种,从而实现自动增量数据转换
上图中的 DDL 语句创建了一个以 town 作为排序键的 AggregatingMergeTree 表,确保数据在磁盘上按该列排序,并生成相应的稀疏主索引。 在 ② 合并过程中,ClickHouse 会将所有排序键相同的行合并为一行,并存储部分聚合状态 (例如,对 avg() 来说会存储一个 sum 和一个 count) 。这些状态可通过增量后台合并确保结果的准确性。
最后修改于 2026年6月10日