跳到主要内容
跳到主要内容

部分合并

ClickHouse 中的部分合并是什么?


ClickHouse 快速 不仅在查询方面表现出色,在插入数据方面也同样如此,这要归功于其 存储层,该层的工作方式类似于 LSM 树

① 插入(到 MergeTree 引擎 系列的表中)会创建排序的、不可变的 数据部分

② 所有数据处理都卸载到 后台部分合并

这使得数据写入变得轻量且 高效

为了控制每个表的 ^^部分^^ 数量,并实现上述的 ②,ClickHouse 持续在后台将较小的 ^^部分^^ 合并成较大的 ^^部分^^,直到它们达到大约 ~150 GB 的压缩大小(按分区进行合并) 。

以下图示画出了这个后台合并过程:

PART MERGES

每次额外合并时,部分的 merge level 增加一个级别。 0 级别表示该部分是新建的,并且尚未合并。被合并到较大 ^^部分^^ 中的 ^^部分^^ 被标记为 非活动,并在经过 可配置 的时间后(默认 8 分钟)最终删除。随着时间的推移,这创建了一个 合并部分的树。因此称之为 合并树 表。

监控合并

部分的定义 示例中,我们 展示 了 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 │
   └─────────────┴───────┴─────────┘

通过 运行 查询,现在显示这四个 ^^部分^^ 已合并为一个最终部分(前提是没有进一步插入到该表):

   ┌─name───────┬─level─┬─────rows─┐
1. │ all_0_23_2 │     2 │ 25248433 │
   └────────────┴───────┴──────────┘

在 ClickHouse 24.10 中,新增了一个 合并仪表板 到内置的 监控仪表板。通过 /merges HTTP 处理器可在 OSS 和云端使用,我们可以使用它来可视化示例表的所有部分合并:

PART MERGES

上述记录的仪表板捕捉了整个过程,从初始数据插入到最终合并为一个部分:

① 活动 ^^部分^^ 的数量。

② 部分合并,以方框的形式可视化(大小反映部分大小)。

写入放大

并发合并

单个 ClickHouse 服务器使用多个后台 合并线程 来执行并发部分合并:

PART MERGES

每个合并线程执行一个循环:

① 决定接下来合并哪 ^^部分^^,并将这些 ^^部分^^ 加载到内存中。

② 将内存中的 ^^部分^^ 合并为一个更大的部分。

③ 将合并后的部分写入磁盘。

返回到 ①

请注意,增加 CPU 核心数量和 RAM 大小可以增加后台合并的吞吐量。

内存优化合并

ClickHouse 并不一定会一次性将所有待合并的 ^^部分^^ 加载到内存中,如 前面的示例 所示。基于多个 因素,为了减少内存消耗(牺牲合并速度),所谓的 垂直合并 以块的形式加载并合并 ^^部分^^ ,而不是一次性完成。

合并机制

下图展示了一个在 ClickHouse 中的后台 合并线程 如何合并 ^^部分^^(默认情况下,不使用 垂直合并):

PART MERGES

部分合并的执行分为几个步骤:

① 解压缩与加载:要合并的 ^^部分^^ 中的 压缩二进制列文件 被解压缩并加载到内存中。

② 合并:数据被合并到更大的列文件中。

③ 索引:为合并后的列文件生成一个新的 稀疏主键索引

④ 压缩与存储:新的列文件和索引被 压缩 并保存到一个新的 目录,表示合并后的数据部分。

来自数据部分的其他 元数据,如二级数据跳过索引、列统计、校验和和最小值-最大值索引,也根据合并后的列文件重新创建。为了简化,我们省略了这些细节。

步骤 ② 的机制取决于使用的具体 MergeTree 引擎,因为不同引擎处理合并的方式不同。例如,如果行过时,可能会聚合或替换行。如前所述,这种方法 将所有数据处理卸载到后台合并中,从而通过保持写操作轻量和高效来实现 超快的插入

接下来,我们将简要概述 ^^MergeTree^^ 系列中特定引擎的合并机制。

标准合并

下图展示了标准 MergeTree 表中 ^^部分^^ 的合并过程:

PART MERGES

上图中的 DDL 语句创建了一个 MergeTree 表,其 ^^排序键^^ 为 (town, street),这意味着磁盘上的数据按这些列排序,生成相应的稀疏主键索引。

① 解压缩的预排序表列被 ② 以保留表的全局排序顺序为前提进行合并,该顺序由表的 ^^排序键^^ 定义,③ 生成一个新的稀疏主键索引,最后 ④ 将合并后的列文件和索引压缩并作为新的数据部分存储到磁盘上。

替换合并

ReplacingMergeTree 表中的部分合并工作方式类似于 标准合并,但只保留每行的最新版本,丢弃旧版本:

PART MERGES

上图中的 DDL 语句创建了一个 ReplacingMergeTree 表,其 ^^排序键^^ 为 (town, street, id),这意味着磁盘上的数据按这些列排序,并生成相应的稀疏主键索引。

合并过程中的 ② 步骤与标准 MergeTree 表类似,合并解压缩的预排序列,同时保持全局排序顺序。

但是,ReplacingMergeTree 删除具有相同 ^^排序键^^ 的重复行,仅保留基于其包含部分的创建时间戳的最新行。


汇总合并

SummingMergeTree 表中,数值数据在 ^^部分^^ 合并过程中会自动汇总:

PART MERGES

上图中的 DDL 语句定义了一个 SummingMergeTree 表,town 为 ^^排序键^^,这意味着磁盘上的数据按该列排序,并相应地创建一个稀疏主键索引。

在合并的 ② 步骤中,ClickHouse 用一行替换所有具有相同 ^^排序键^^ 的行,并对数值列的值进行求和。

聚合合并

上面的 SummingMergeTree 表示例是 AggregatingMergeTree 表的一个专门变体,允许通过在部分合并过程中应用任何 90+ 个聚合函数来实现 自动增量数据转换

PART MERGES

上图中的 DDL 语句创建了一个 AggregatingMergeTree 表,town 为 ^^排序键^^,确保数据在磁盘上按此列排序,并生成相应的稀疏主键索引。

在合并的 ② 步骤中,ClickHouse 用一行替换所有具有相同 ^^排序键^^ 的行,存储 部分聚合状态(例如,对于 avg()sumcount)。这些状态通过增量后台合并确保了结果的准确性。