MergeTree原理解析

MergeTree引擎详解
本文深入解析ClickHouse的MergeTree表引擎,涵盖主键索引、数据分区、数据副本及采样等特性。介绍MergeTree的创建方式、存储结构,以及索引、分区和数据存储的细节。

MergeTree原理解析

表引擎决定了一张数据表最终的性格.

MergeTree提供了:

1、主键索引

2、数据分区

3、数据副本

4、数据采样

1. MergeTree的创建方式与存储结构

1.1 MergeTree的创建方式:

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],
    ...
    INDEX index_name1 expr1 TYPE type1(...) GRANULARITY value1,
    INDEX index_name2 expr2 TYPE type2(...) GRANULARITY value2
) 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, ...]

[PARTITION BY expr]

[PRIMARY KEY expr]

[SAMPLE BY expr]

[TTL expr [DELETE|TO DISK ‘xxx’|TO VOLUME ‘xxx’], …]

[SETTINGS name=value, …]

  • ENGINE - 引擎名和参数。 ENGINE = MergeTree(). MergeTree 引擎没有参数。

  • PARTITION BY — 分区键 。

    要按月分区,可以使用表达式 `toYYYYMM(date_column)` ,这里的 `date_column` 是一个 [Date](../../../engines/table_engines/mergetree_family/mergetree.md) 类型的列。这里该分区名格式会是 `"YYYYMM"` 这样。
    
  • ORDER BY — 表的排序键。必选!

    可以是一组列的元组或任意的表达式。 例如: `ORDER BY (CounterID, EventDate)` 。
    
  • PRIMARY KEY - 主键,如果要设成 跟排序键不相同。

    默认情况下主键跟排序键(由 `ORDER BY` 子句指定)相同。
    因此,大部分情况下不需要再专门指定一个 `PRIMARY KEY` 子句。
    
  • SAMPLE BY — 用于抽样的表达式。

    如果要用抽样表达式,主键中必须包含这个表达式。例如:
    `SAMPLE BY intHash32(UserID) ORDER BY (CounterID, EventDate, intHash32(UserID))` 。
    
  • SETTINGS — 影响 MergeTree 性能的额外参数:

    • index_granularity — 索引粒度。即索引中相邻『标记』间的数据行数。默认值,8192 。该列表中所有可用的参数可以从这里查看 MergeTreeSettings.h
    • index_granularity_bytes — 索引粒度,以字节为单位,默认值: 10Mb。如果仅按数据行数限制索引粒度, 请设置为0(不建议)。
    • enable_mixed_granularity_parts — 启用或禁用通过 index_granularity_bytes 控制索引粒度的大小。在19.11版本之前, 只有 index_granularity 配置能够用于限制索引粒度的大小。当从大表(数十或数百兆)中查询数据时候,index_granularity_bytes 配置能够提升ClickHouse的性能。如果你的表内数据量很大,可以开启这项配置用以提升SELECT 查询的性能。
    • use_minimalistic_part_header_in_zookeeper — 数据片段头在 ZooKeeper 中的存储方式。如果设置了 use_minimalistic_part_header_in_zookeeper=1 ,ZooKeeper 会存储更少的数据。更多信息参考『服务配置参数』这章中的 设置描述
    • min_merge_bytes_to_use_direct_io — 使用直接 I/O 来操作磁盘的合并操作时要求的最小数据量。合并数据片段时,ClickHouse 会计算要被合并的所有数据的总存储空间。如果大小超过了 min_merge_bytes_to_use_direct_io 设置的字节数,则 ClickHouse 将使用直接 I/O 接口(O_DIRECT 选项)对磁盘读写。如果设置 min_merge_bytes_to_use_direct_io = 0 ,则会禁用直接 I/O。默认值:10 * 1024 * 1024 * 1024 字节。
    • merge_with_ttl_timeout — TTL合并频率的最小间隔时间。默认值: 86400 (1 天)。
    • write_final_mark — 启用或禁用在数据片段尾部写入最终索引标记。默认值: 1(不建议更改)。
    • storage_policy — 存储策略。 参见 使用多个区块装置进行数据存储.

1.2 MergeTree的存储结构

2、数据分区

数据是以分区目录的形式组织的,每个分区独立分开存储。

这种形式,查询数据时,可以有效的跳过无用的数据文件。

2.1 数据分区的规则

分区键的取值,生成分区ID,分区根据ID决定。根据分区键的数据类型不同,分区ID的生成目前有四种规则:

(1)不指定分区键

(2)使用整形

(3)使用日期类型

(4)使用其他类型

数据在写入时,会对照分区ID落入对应的分区。

2.2 分区目录的生成规则

partitionID_MinBlockNum_MaxBlockNum_Level

MinBlockNum:最小数据块编号

MaxBlockNum:最大数据块编号

对于一个新的分区,MinBlockNum和MaxBlockNum的值相同

2.3 分区目录的合并过程

MergeTree的分区目录在数据写入过程中被创建。

不同的批次写入数据属于同一分区,也会生成不同的目录,在之后的某个时刻再合并

合并以后的命名规则:

MinBlockNum:取同一分区中MinBlockNum值最小的

MaxBlockNum:取同一分区中MaxBlockNum值最大的

Level:取同一分区最大的Level值加1

3、索引

3.1 一级索引

文件:primary.idx

MergeTree的主键使用Primary Key定义,主键定义之后,MergeTree会根据index_granularity间隔(默认8192)为数据生成一级索引并保存至primary.idx文件中。

简化形式:通过order by指代主键

3.1.1 稀疏索引

primary.idx文件的一级索引采用稀疏索引。

稠密索引:每一行索引标记对应一行具体的数据记录

稀疏索引:每一行索引标记对应一段数据记录(默认索引粒度为8192)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9euLjplb-1597834654787)(C:\Users\EDZ\Documents\ClickHouse\myCH\资源\稀疏索引和密集索引的区别.png)]

稀疏索引占用空间小,所以primary.idx内的索引数据常驻内存,取用速度快!

3.1.2 索引粒度

inde_granularity参数,表示索引粒度。新版本中clickhouse提供了自适应索引粒度。

索引粒度在MergeTree引擎中很重要。

3.1.3 索引数据的生成规则

primary.idx文件

由于稀疏索引,所以MergeTree要间隔index_granularity行数据才会生成一个索引记录,其索引值会根据声明的主键字段获取。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8yat7YxH-1597834654789)(C:\Users\EDZ\Documents\ClickHouse\myCH\资源\索引数据的生成规则.jpg)]

3.1.4 索引的查询过程

索引是如何工作的?

MarkRange:

按照index_granularity的间隔粒度,将一段完整的数据划分成多个小的数据段,小的数据段就是MarkRange,

MarkRange与索引编号对应。

案例:

共200行数据

index_granularity大小为5

主键ID为Int,取值从0开始

根据索引生成规则,primary.idx文件内容为:

在ClickHouse中,MergeTree引擎表的索引列在建表时使用ORDER BY语法来指定。而在官方文档中,用了下面一幅图来说明。

在这里插入图片描述

这张图示出了以CounterID、Date两列为索引列的情况,即先以CounterID为主要关键字排序,再以Date为次要关键字排序,最后用两列的组合作为索引键。marks与mark numbers就是索引标记,且marks之间的间隔就由建表时的索引粒度参数index_granularity来指定,默认值为8192。

ClickHouse MergeTree引擎表中,每个part的数据大致以下面的结构存储。

.
├── business_area_id.bin
├── business_area_id.mrk2
├── coupon_money.bin
├── coupon_money.mrk2
├── groupon_id.bin
├── groupon_id.mrk2
├── is_new_order.bin
├── is_new_order.mrk2

├── primary.idx

其中,bin文件存储的是每一列的原始数据(可能被压缩存储),mrk2文件存储的是图中的mark numbers与bin文件中数据位置的映射关系。另外,还有一个primary.idx文件存储被索引列的具体数据。另外,每个part的数据都存储在单独的目录中,目录名形如20200708_92_121_7,即包含了分区键、起始mark number和结束mark number,方便定位。

在ClickHouse之父Alexey Milovidov分享的PPT中,有更加详细的图示。

在这里插入图片描述

这样,每一列都通过ORDER BY列进行了索引。查询时,先查找到数据所在的parts,再通过mrk2文件确定bin文件中数据的范围即可。

不过,ClickHouse的稀疏索引与Kafka的稀疏索引不同,可以由用户自由组合多列,因此也要格外注意不要加入太多索引列,防止索引数据过于稀疏,增大存储和查找成本。另外,基数太小(即区分度太低)的列不适合做索引列,因为很可能横跨多个mark的值仍然相同,没有索引的意义了。

3.2 跳数索引

granularity和index_granularity的关系

index_granularity定义了数据的粒度
granularity定义了聚合信息汇总的粒度
换言之,granularity定义了一行跳数索引能够跳过多少个index_granularity区间的数据

索引的可用类型
  • minmax
    存储指定表达式的极值(如果表达式是 tuple ,则存储 tuple 中每个元素的极值),这些信息用于跳过数据块,类似主键。
  • set(max_rows)
    存储指定表达式的惟一值(不超过 max_rows 个,max_rows=0 则表示『无限制』)。这些信息可用于检查 WHERE 表达式是否满足某个数据块。
  • ngrambf_v1(n, size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed)
    存储包含数据块中所有 n 元短语的 布隆过滤器 。只可用在字符串上。
    可用于优化 equalslikein 表达式的性能。
    n – 短语长度。
    size_of_bloom_filter_in_bytes – 布隆过滤器大小,单位字节。(因为压缩得好,可以指定比较大的值,如256或512)。
    number_of_hash_functions – 布隆过滤器中使用的 hash 函数的个数。
    random_seed – hash 函数的随机种子。
  • tokenbf_v1(size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed)
    ngrambf_v1 类似,不同于 ngrams 存储字符串指定长度的所有片段。它只存储被非字母数据字符分割的片段。
INDEX sample_index (u64 * length(s)) TYPE minmax GRANULARITY 4
INDEX sample_index2 (u64 * length(str), i32 + f64 * 100, date, str) TYPE set(100) GRANULARITY 4
INDEX sample_index3 (lower(str), str) TYPE ngrambf_v1(3, 256, 2, 0) GRANULARITY 4

4 、数据存储

表由按主键排序的数据 片段 组成。

当数据被插入到表中时,会分成数据片段并按主键的字典序排序。例如,主键是 (CounterID, Date) 时,片段中数据按 CounterID 排序,具有相同 CounterID 的部分按 Date 排序。

不同分区的数据会被分成不同的片段,ClickHouse 在后台合并数据片段以便更高效存储。不会合并来自不同分区的数据片段。这个合并机制并不保证相同主键的所有行都会合并到同一个数据片段中。

ClickHouse 会为每个数据片段创建一个索引文件,索引文件包含每个索引行(『标记』)的主键值。索引行号定义为 n * index_granularity 。最大的 n 等于总行数除以 index_granularity 的值的整数部分。对于每列,跟主键相同的索引行处也会写入『标记』。这些『标记』让你可以直接找到数据所在的列。

你可以只用一单一大表并不断地一块块往里面加入数据 – MergeTree 引擎的就是为了这样的场景

4.1 按列存储

在MergeTree中数据按列存储,具体到每个列字段,都拥有一个.bin数据文件,是最终存储数据的文件。
按列存储的好处:
1、更好的压缩
2、最小化数据扫描范围

4.2 压缩数据块

一个压缩数据块有两部分组成:
1、头信息
2、压缩数据

5、数据标记

.mrk文件

压缩文件中的偏移量解压缩块中的偏移量
00
08192
016384
120160
120168192

5.1 .mrk文件内容的生成规则

5.2 .mrk文件的工作方式

6、分区、索引、标记和压缩协同

6.1、写入过程

6.2、查询过程

6.3 数据标记与压缩数据块的对应关系

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值