Clickhouse MergeTree索引原理

作者: LemonNan

注: 转载需注明作者及原文地址

介绍

本文介绍 Clickhouse 的 MergeTree 引擎的存储结构以及索引原理,MergeTree 顾名思义,需要进行 Merge,在介绍具体内容前先介绍一些前置概念:

  • partition:数据分区

  • Block: 数据文件的压缩单元,一个数据文件 *.bin 里包含一个或多个 Block,一个 Block 可能包含一个或多个 granularity

  • granularity: 颗粒度,实现索引的颗粒度大小

相关参数

下面这些参数都跟索引有关,所以先放在这了,当然没有展示所有的参数,只列了个人觉得比较重要的几个

  • index_granularity, 默认8192,表示8192行数据为一个颗粒度

  • index_granularity_bytes, 默认10M,表示数据颗粒度最大大小为 10M ,如果设置为0,则只根据 index_granularity 限制颗粒度大小

  • min_index_granularity_bytes,默认 1024b,表示允许最小的数据颗粒度大小

  • storage_policy,存储策略,可以通过配置该策略使用多磁盘存储(包含冷热数据等)

  • max_compress_block_size,block 未压缩前数据的最大大小

  • min_compress_block_size,block 未压缩前数据的最小大小

索引原理

Clickhouse 的索引包括主键索引和 Skipping 索引。这里先放一张索引结构图(skipping 索引使用 minmax,GRANULARITY= 3),一张图的效果能顶的上很多文字描述。

数据写入

MergeTree 在数据插入之前,先将数据根据 主键字段(主键字段默认跟 Order by 相同,但是可以手动指定为 Order by 字段集的前缀) 排好序,然后对数据进行 有序标记及存储,数据有序插入可以有几个好处:快速查找以及方便数据压缩,毕竟排过序后相同数据的压缩效率是很高的。

由于每次插入都会生成一个单独的数据分区,在随机I/O的情况下,如果每次插入的数据量过小,会严重的影响性能,解决方式可以在客户端进行数据缓存,然后再批量进行数据插入,这样可以提升不少的效率,但是这样的话,数据的实时性就会受到影响,所以不太适合实时性要求非常高的场景。

数据读取

由于 primary 和 mrk/mrk2 的顺序是一致的,所以在查找的时候,只需根据主键查找到对应的位置,并且在mrk/mrk2 也读取相同位置的数据:Block 在 bin 中的偏移量 和 对应 granule 在 Block 中的偏移量,就能缩小范围定位到 granule,加快查找。

图上只表示了一个字段的情况,如果是多个字段的话,情况也是一样的,因为不管多少个字段, mrk2 的文件内容的顺序都是跟主键顺序(primary.idx 文件内容)一一对应,可以直接读取,这就是有序的一个好处,可以很方便的查找到对应标记文件内的数据,然后读取检索符合条件的 granule 数据。

Merge 操作

每次插入单独生成一个分区,数据会在后台进行 异步的分区 Merge 操作,将多个数据分区的数据合并,生成新的数据分区,最终分区表达式相同的数据会合并到同一个大的分区中。比如之前的例子使用 PARTITION BY toYYYYMM(EventDate) 分区表达式,则 最终所有相同月份的数据会合并到一个大的分区中

关于分区名

根据官方的数据导入之后,最终的分区名为:201403_1_29_2

分区名规则:{分区表达式的值}_{minBlockNum}_{maxBlockNum}_{Level}

{分区表达式的值} = PARTITION BY toYYYYMM(EventDate)

minBlockNum = 数据块的最小编号

maxBlockNum = 数据块的最大编号

{Level} = 合并分区前最大 Level +1,也就是表示合并后的 Level

Skipping 索引

Skipping 索引实质上跟主键索引类似, 它是 在主键索引的基础上, 提供更加丰富的功能, 比如它的索引方法有:minmax、set、bloom_filter、ngrambf_v1等。

定义 Skipping 索引

INDEX index_name expr TYPE type(...) GRANULARITY {granularity_value}

{granularity_value} 表示多少个 index_granularity 才生成一条 skipping 索引记录。

假设 granularity_value = 3, index_granularity = 8192, 则每隔 3 * 8192 行数据会生成一条 skipping 索引记录。

Skipping 索引的作用过程

假设 type = minmax 作为例子,假设通过主键索引查找到数据位于 [range15, range18] 的区间,查找的对应字段的值为 998,此时 [range15:[10,200],range16[100, 1000],range17[300,600],range18[500,1000]],而查询的 998 的值通过对应的minmax,只需检索 range16 和 range18 的里面的数据即可,虽然实际上并没有那么精确,但在某些实际情况下可以减少检索的数据量加快查询。

总结

  • MergeTree 通过插入前排序、批量有序的标记、插入,尽可能减少磁盘性能下降,方便数据检索以及数据压缩

  • skipping 索引在主键索引的功能上,添加额外的功能,虽然实现相对来说并不细,但是根据具体场景、数据特点设计,是可以加快查询速度

最后

欢迎搜索公众号 LemonCode , 一起交流学习!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬楠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值