ClickHouse 中的 part 合并是什么?
ClickHouse 速度很快,不仅查询快,插入也快,这要归功于其存储层。它的工作方式与 LSM trees 类似: ① 插入到 MergeTree 引擎家族的表中时,会创建经过排序且不可变的数据分区片段。 ② 所有数据处理都会交由后台 part 合并完成。 这使得数据写入开销很低,而且效率极高。 为了控制每个表的分片数量并实现上面的 ②,ClickHouse 会在后台持续将较小的分片 (按分区) 合并为较大的分片,直到其 compressed size 达到约 ~150 GB。 下图概述了这一后台合并过程:
分片的
merge level 每经历一次额外的合并就会加一。level 为 0 表示该分片是新的,尚未被合并。已合并到更大分片中的分片会被标记为 inactive,并在一段可配置的时间后最终删除 (默认 8 分钟) 。随着时间推移,这会形成一棵由合并后分片构成的树。这也正是 merge tree 表名称的由来。
监控合并
/merges HTTP handler 访问,我们可以用它来可视化示例表中的所有 part 合并:
上方录制的仪表盘展示了从最初插入数据到最终合并为单个分片的整个过程: ① 活跃分片的数量。 ② part 合并,以方框形式展示 (大小反映分片的大小) 。 ③ 写入放大。
并发合并
每个合并线程都会执行以下循环: ① 决定下一步要合并哪些分片,并将这些分片加载到内存中。 ② 在内存中将这些分片合并为一个更大的分片。 ③ 将合并后的分片写入磁盘。 返回 ① 请注意,增加 CPU 核心数量和 RAM 容量可以提升后台合并吞吐量。
内存优化合并
合并机制
part 合并分为以下几个步骤: ① 解压与加载:待合并分片中的压缩二进制列文件会先解压并加载到内存中。 ② 合并:数据会被合并成更大的列文件。 ③ 索引:系统会为合并后的列文件生成新的稀疏主索引。 ④ 压缩与存储:新的列文件和索引会被压缩,并保存在一个新的目录中,该目录表示合并后的数据分片。 数据分片中的其他元数据 (如二级数据跳过索引、列统计信息、校验和以及 min-max 索引) 也会基于合并后的列文件重新创建。为简化说明,我们省略了这些细节。 步骤 ② 的具体机制取决于所使用的 MergeTree 引擎,因为不同引擎处理合并的方式不同。例如,行可能会被聚合;如果数据已过期,也可能会被替换。正如前文所述,这种方法会将所有数据处理都交由后台合并完成,从而让写入操作保持轻量高效,实现超高速插入。 接下来,我们将简要介绍 MergeTree 家族中几种特定引擎的合并机制。
标准合并
上图中的 DDL 语句创建了一个排序键为
(town, street) 的 MergeTree 表,这意味着 磁盘上的数据会按这些列排序,并据此生成稀疏主索引。
① 解压后的预排序表列会在保持由表排序键定义的全局排序顺序的同时进行 ② 合并,③ 生成新的稀疏主索引,然后 ④ 将合并后的列文件和索引压缩,并作为新的数据分片存储到磁盘上。
Replacing 合并
上图中的 DDL 语句创建了一个
ReplacingMergeTree 表,其排序键为 (town, street, id);这意味着磁盘上的数据会按这些列排序,并据此生成稀疏主索引。
② 合并的工作方式与标准 MergeTree 表类似:在保持全局排序顺序的同时,合并已解压且预先排序的列。
不过,ReplacingMergeTree 会删除具有相同排序键的重复行,只保留最新的一行;这里的“最新”以其所在数据分片的创建时间戳为准。
求和合并
上图中的 DDL 语句定义了一个
SummingMergeTree 表,并将 town 设为排序键,这意味着磁盘上的数据会按该列排序,并据此创建稀疏主索引。
在 ② 合并步骤中,ClickHouse 会将所有具有相同排序键的行替换为一行,并对数值列的值求和。
聚合合并
SummingMergeTree 表示例是 AggregatingMergeTree 表的一种特化变体,它支持在part 合并期间应用 90+ 种聚合函数中的任意一种,从而实现自动增量数据转换:
上图中的 DDL 语句创建了一个以
town 作为排序键的 AggregatingMergeTree 表,确保数据在磁盘上按该列排序,并生成相应的稀疏主索引。
在 ② 合并过程中,ClickHouse 会将所有排序键相同的行合并为一行,并存储部分聚合状态 (例如,对 avg() 来说会存储一个 sum 和一个 count) 。这些状态可通过增量后台合并确保结果的准确性。