关于数据库:ClickHouse核心引擎MergeTree解读

6次阅读

共计 5813 个字符,预计需要花费 15 分钟才能阅读完成。

ClickHouse 是俄罗斯最大的搜索引擎 Yandex 在 2016 年开源的数据库管理系统(DBMS),次要用于联机剖析解决(OLAP)。其采纳了面向列的存储形式,性能远超传统面向行的 DBMS,近几年受到宽泛关注。

本文将介绍 ClickHouse MergeTree 系列表引擎 的相干常识,并通过示例剖析 MergeTree 存储引擎的数据存储构造。

1 MergeTree 表引擎简介

MergeTree(合并树)系列表引擎是 ClickHouse 提供的最具特色的存储引擎。MergeTree 引擎反对数据按主键、数据分区、数据正本以及数据采样等个性。官网提供了包含 MergeTree、ReplacingMergeTree、SummingMergeTree、AggregatingMergeTree、CollapsingMergeTree、VersionedCollapsingMergeTree、GraphiteMergeTree 等 7 种不同类型的 MergeTree 引擎的实现,以及与其绝对应的反对数据正本的 MergeTree 引擎(Replicated*)。

首先来介绍一下MergeTree 外围引擎:

ReplacingMergeTree在后盾数据合并期间,对具备雷同排序键的数据进行去重操作。

SummingMergeTree:当合并数据时,会把具备雷同主键的记录合并为一条记录。依据聚合字段设置,该字段的值为聚合后的汇总值,非聚合字段应用第一条记录的值,聚合字段类型必须为数值类型。

AggregatingMergeTree:在同一数据分区下,能够将具备雷同主键的数据进行聚合。

CollapsingMergeTree:在同一数据分区下,对具备雷同主键的数据进行折叠合并。

VersionedCollapsingMergeTree:

基于 CollapsingMergeTree 引擎,削减了数据版本信息字段配置选项。在数据根据 ORDER BY 设置对数据进行排序的根底上,如果数据的版本信息列不在排序字段中,那么版本信息会被隐式的作为 ORDER BY 的最初一列从而影响数据排序。

GraphiteMergeTree:用来存储时序数据库 Graphites 的数据。

MergeTree 是该系列引擎中最外围的引擎,其余引擎均以 MergeTree 为根底,并在数据合并过程中实现了不同的个性,从而形成了 MergeTree 表引擎家族。上面咱们通过 MergeTree 来具体理解 MergeTree 表系列引擎。

2 MergeTree 引擎

2.1 表创立

创立 MergeTree 的 DDL 如下所示:

CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster] 
(name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1], 
 name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2], 
 ... 
) ENGINE = MergeTree()
 ORDER BY expr 
 [PARTITION BY expr] 
 [PRIMARY KEY expr] 
 [SAMPLE BY expr] 
 [TTL expr [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'], ...] 
 [SETTINGS name=value, ... 

这里阐明一下 MergeTree 引擎的主要参数:

必填选项

ENGINE:引擎名字,MergeTree 引擎无参数。

ORDER BY:排序键,能够由一列或多列组成,决定了数据以何种形式进行排序,例如 ORDER BY(CounterID, EventDate)。如果没有显示指定 PRIMARY KEY,那么将应用 ORDER BY 作为 PRIMARY KEY。通常只指定 ORDER BY 即可。

选填选项

PARTITION BY:分区键,指明表中的数据以何种规定进行分区。分区是在一个表中通过指定的规定划分而成的逻辑数据集。分区能够按任意规范进行,如按月、按日或按事件类型。为了缩小须要操作的数据,每个分区都是离开存储的。

PRIMARY KEY:主键,设置后会依照主键生成一级索引(primary.idx),数据会根据索引的设置进行排序,从而减速查问性能。默认状况下,PRIMARY KEY 与 ORDER BY 设置雷同,所以通常状况下间接应用 ORDER BY 设置来代替主键设置。

SAMPLE BY:数据采样设置,如果显示配置了该选项,那么主键配置中也应该包含此配置。例如 ORDER BY CounterID / EventDate / intHash32(UserID)、SAMPLE BY intHash32(UserID)。

TTL:数据存活工夫,能够为某一字段列或者一整张表设置 TTL,设置中必须蕴含 Date 或 DateTime 字段类型。如果设置在列上,那么会删除字段中过期的数据。如果设置的是表级的 TTL,那么会删除表中过期的数据。如果设置了两种类型,那么按先到期的为准。例如,TTL createtime + INTERVAL 1 DAY,即一天后过期。应用场景包含定期删除数据,或者定期将数据进行归档。

index_granularity:索引距离粒度。MergeTree 索引为稠密索引,每 index_granularity 个数据产生一条索引。index_granularity 默认设置为 8092。

enable_mixed_granularity_parts:是否启动 index_granularity_bytes 来管制索引粒度大小。

index_granularity_bytes:索引粒度,以字节为单位,默认 10Mb。

merge_max_block_size:数据块合并最大记录个数,默认 8192。

merge_with_ttl_timeout:合并频率最小工夫距离,默认 1 天。

2.2 数据存储构造

首先创立一个 test 表,DDL 如下:

CREATE TABLE test.test 
( 
 id        UInt64, 
 type      UInt8, 
 create_time DateTime 
) ENGINE = MergeTree() 
 PARTITION BY toYYYYMMDD(create_time) 
 ORDER BY (id) 
 SETTINGS index_granularity = 4;

test 表包含 id、type、create 等三个字段,其中以 create_time 日期字段作为分区键,并将日期格局转化为 YYYYMMDD。依照 id 字段进行排序。因为没有显式设置主键,所以引擎默认应用 ORDER BY 设置的 id 列作为索引字段,并生成索引文件。index_granularity 设置为 4,意味着每 4 条数据产生一条索引数据。

插入一条测试数据:

insert into test.test(id, type, create_time) VALUES (1, 1, toDateTime('2021-03-01 00:00:00'));

应用如下命令查看 test 表分区相干信息:

 SELECT 
 database, 
 table, 
 partition, 
 partition_id, 
 name, 
 active, 
 path 
 FROM system.parts 
 WHERE table = 'test' 

返回后果如下图所示:

从上图中能够看到 test 表中返回了一条 partitionid 为 20210301 的数据分区的记录,从 name 字段中咱们能够得悉,此分区的目录名为 20210301_8_8_0。20210301_8_8_0 这个目录名字到底有什么含意呢?上面来介绍一下分区规定以及分区目录的命名规定。

2.2.1 数据分区 ID 生成规定

数据分区规定由分区 ID 决定,分区 ID 由 PARTITION BY 分区键决定。依据分区键字段类型,ID 生成规定可分为:

未定义分区键

没有定义 PARTITION BY,默认生成一个目录名为 all 的数据分区,所有数据均寄存在 all 目录下。

整型分区键

分区键为整型,那么间接用该整型值的字符串模式做为分区 ID。

日期类分区键

分区键为日期类型,或者能够转化成日期类型。

其余类型分区键

String、Float 类型等,通过 128 位的 Hash 算法取其 Hash 值作为分区 ID。

下面咱们插入一条日期为 2021-03-01 00:00:00 的数据,对该字段格式化后生成的数据分区 id 就是 20210301。

2.2.2 数据分区目录命名规定

目录命名规定如下:

PartitionId_MinBlockNum_MaxBlockNum_Level

PartitionID

分区 id,例如 20210301。

MinBlockNum

最小分区块编号,自增类型,从 1 开始向上递增。每产生一个新的目录分区就向上递增一个数字。

MaxBlockNum

最大分区块编号,新创建的分区 MinBlockNum 等于 MaxBlockNum 的编号。

Level

合并的层级,被合并的次数。合并次数越多,层级值越大。

从上图可知,此分区的分区 id 为 20210301,以后分区的 MinBlockNum 和 MinBlockNum 均为 8,而 level 为 0,示意此分区没有合并过。

2.3 数据分区文件组织构造

在理解了分区目录名字的生成规定后,上面来看看数据分区目录下的文件组织构造。以 2021030188_0 分区为例:

从图中能够看到,目录中的文件次要包含 bin 文件、mrk 文件、primary.idx 文件以及其余相干文件。

bin 文件

数据文件,存储的是某一列的数据。数据表中的每一列都对应一个与其字段名雷同的 bin 文件,例如 id.bin 存储的是表 test 中 id 列的数据。

mrk 文件

标记文件,每一列都对应一个与其字段名雷同的标记文件,标记文件在 idx 索引文件和 bin 数据文件之间起到了桥梁作用。以 mrk2 结尾的文件,示意该表启用了自适应索引距离。

primary.idx 文件

主键索引文件,用于放慢查问效率。

count.txt

数据分区中数据总记录数。上述 20210301_8_8_0 的数据分区中,该文件中的记录总数为 1。

columns.txt

表中所有列数的信息,包含字段名和字段类型。

partion.dat

用于保留分区表达式的值。上述 20210301_8_8_0 的数据分区中该文件中的值为 20210301。

minmax_create_time.idx

分区键的最大最小值。

checksums.txt

校验文件,用于校验各个文件的正确性。寄存各个文件的 size 以及 hash 值。

2.3.1 数据文件

MergeTree 中,每列都对应一个 bin 文件独自寄存该列数据。例如,id.bin 寄存的是 id 列的数据。所有数据都通过数据压缩、排序,最初以数据块的模式写入 bin 文件中。bin 中数据以压缩数据块为单位写入文件中。每个数据块由头信息和压缩数据组成。头部信息包含校验和、数据压缩算法、数据压缩前大小和压缩后大小组成。压缩数据由 granule 组成,granule 大小与 index_granularity 相干。

2.3.2 索引文件

MergeTree 索引为稠密索引,它并不索引单条数据,而是索引肯定范畴的数据。也就是从已排序的全量数据中,间隔性的选取一些数据记录主键字段的值来生成 primary.idx 索引文件,从而放慢表查问效率。距离设置参数为 index_granularity。

咱们向表 test 中插入 9 条数据,

insert into test.test(id, type, create_time) VALUES (1, 1, toDateTime('2021-03-01 00:00:00')); 
insert into test.test(id, type, create_time) VALUES (1, 2, toDateTime('2021-03-01 00:00:00')); 
insert into test.test(id, type, create_time) VALUES (1, 3, toDateTime('2021-03-01 00:00:00')); 
insert into test.test(id, type, create_time) VALUES (2, 1, toDateTime('2021-03-01 00:00:00')); 
insert into test.test(id, type, create_time) VALUES (2, 1, toDateTime('2021-03-01 00:00:00')); 
insert into test.test(id, type, create_time) VALUES (3, 1, toDateTime('2021-03-01 00:00:00')); 
insert into test.test(id, type, create_time) VALUES (3, 1, toDateTime('2021-03-01 00:00:00')); 
insert into test.test(id, type, create_time) VALUES (4, 1, toDateTime('2021-03-01 00:00:00')); 
insert into test.test(id, type, create_time) VALUES (5, 1, toDateTime('2021-03-01 00:00:00'));

因为 index_granularity 设置为 4,所以每 4 条数据就会生成一条索引记录,即应用插入的第 1、5、9 条数据 id 字段的值生成索引文件记录。

2.3.3 标记文件

mrk 标记文件在 primary.idx 索引文件和 bin 数据文件之间起到了桥梁作用。primary.idx 文件中的每条索引在 mrk 文件中都有对应的一条记录。一条记录的组成包含:

offset-compressed bin file

示意指向的压缩数据块在 bin 文件中的偏移量。

offset-decompressed data block

示意指向的数据在解压数据块中的偏移量。

row counts

代表数据记录行数,小于等于 index_granularity 所设置的值。

索引,标记和数据文件下图所示:


作者:TalkingData 张凯

参考文档:

1.https://clickhouse.tech/docs

2.http://www.clickhouse.com.cn/…

3.《ClickHouse 原理解析与利用实际》(机械工业出版社出版,作者朱凯)

正文完
 0