Clickhouse MergeTree引擎原理[读书笔记]

概述

Clickhouse作为一个数据库,主要用于管理数据的结构化存储,优化查询等操作。

对于引擎的知识点,需要搞明白:

  • 创建方式,使用方式
  • 数据存储结构
  • 数据查询原理

ClickHouse拥有非常庞大的表引擎体系,主要包括6大类:MergeTree家族、外部存储、内存、文件、接口和其他。其中由于合并树包括ALTER相关操作、主键索引、数据分区、数据副本和数据采样等完整特性,是Clickhouse的核心内容,本文主要梳理MergeTree引擎及其派生引擎的知识点。

MergeTree顾名思义:合并树,通过合并完成查询、索引、分区等相关操作。

创建&使用

MergeTree在写入一批数据时,数据总会以数据片段的形式写入磁盘,且数据片段不可修改。属于相同分区的数据片段会定期被合成一个新的片段。这种数据片段往复合并的特点,也正是合并树名称的由来。

建表语句:

CREATE TABLE [IF NOT EXISTS] [db_name.]table_name (
    name1 [type] [DEFAULT|MATERIALIZED|ALIAS expr],
    name2 [type] [DEFAULT|MATERIALIZED|ALIAS expr],
    省略...) 
ENGINE = MergeTree()
[PARTITION BY expr]
[ORDER BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[SETTINGS name=value, 省略...
  • Partition by :分区键

  • ORDER BY:排序键,组合键

  • PRIMARY KEY:主键,注:主键必须是排序键的子集

  • SAMPLE BY:抽样表达式

抽样表达式需要配合SAMPLE子查询使用,这项功能对于选取抽样数据十分有用。

文件结构

MergeTree是由特殊设计的文件存储方式实现的,核心知识点主要包括:

  • 分区
  • 索引
  • 数据压缩块
    • 压缩算法
    • 文件结构
  • 数据标记

其中,分区作为文件夹划分的依据,每个分区独立分开存储。每个分区一个独立的索引文件,真实数据按列进行存储,并以块为单位进行压缩存储。数据标记记录索引记录和文件块的匹配关系。

通过上述一套组合拳,在每次发起查询请求时,能够最大限度的剔除无效数据,只读取有用的数据块,这也是Clickhouse读取高效的关键。

目录结构

Clickhouse的原始数据结构:${clickhouse_data}/db.name/table.name/{分区路径}/{各类数据}

分区
分区目录结构

数据是以分区目录的形式进行组织的,每个分区目录下包含索引、数据块等文件。完整的存储结构如下:
在这里插入图片描述

一张数据表的完整物理结构分为3个层级,依次是数据表目录(库/表)、分区目录及各分区下具体的数据文件。接下来就逐一介绍它们的作用。

  • partition:分区目录,余下各类数据文件(primary.idx、[Column].mrk、[Column].bin等)都是以分区目录的形式被组织存放的,相同分区数据会被合并到同一个分区目录;

  • checksums.txt:校验文件,使用二进制格式存储。它保存了余下各类文件(primary.idx、count.txt等)的size大小及size的哈希值,用于快速校验文件的完整性和正确性。

  • columns.txt:列信息文件,使用明文格式存储。用于保存此数据分区下的列字段信息。

  • count.txt:计数文件,使用明文格式存储。用于记录当前数据分区目录下数据的总行数。

  • primary.idx:一级索引文件,使用二进制格式存储。用于存放 稀疏索引 ,借助稀疏索引,在数据查询的时能够排除主键条件范围之外的数据文件;

  • [Column].bin:数据文件,使用压缩格式存储,默认为LZ4压缩格式,每一个列字段都拥有独立的.bin数据文件,并以列字段名称命名;

  • [Column].mrk:列字段数据标记文件,使用二进制格式存储。标记文件中保存了.bin文件中数据的偏移量信息。标记文件与稀疏索引对齐,又与.bin文件一一对应,所以MergeTree通过标记文件建立了primary.idx稀疏索引与.bin数据文件之间的映射关系;

  • partition.dat与minmax[Column].idx:如果使用了分区键,例如PARTITION BY EventTime,则会额外生成partition.dat与minmax索引文件,它们均使用二进制格式存储。partition.dat用于保存当前分区下分区表达式最终生成的值;而minmax索引用于记录当前分区下分区字段对应原始数据的最小和最大值;

  • skp_idx_[Column].idx与skp_idx_[Column].mrk:如果在建表语句中声明了二级索引,则会额外生成相应的二级索引与标记文件,它们同样也使用二进制存储。二级索引在ClickHouse中又称跳数索引。

**注:**如果不使用分区键,即不使用PARTITION BY声明任何分区表达式,则分区ID默认取名为all,所有的数据都会被写入这个all分区。

还是有分区概念,只不过默认为一个分区。

分区目录名:PartitionID_MinBlockNum_MaxBlockNum_Level

PartitionID:分区值

MinBlockNum和MaxBlockNum:顾名思义,最小数据块编号与最大数据块编号。

计数n在单张MergeTree数据表内全局累加,n从1开始,每当新创建一个分区目录时,计数n就会累积加1。对于一个新的分区目录而言,MinBlockNum与MaxBlockNum取值一样,同等于n。

Level:目前合并的层级,分区合并过的次数,也就是“版本号”。

例如:201905_1_2_0

分区合并的过程

伴随着每一批数据的写入(一次INSERT语句),MergeTree都会生成一批新的分区目录。即便不同批次写入的数据属于相同分区,也会生成不同的分区目录。

在之后的某个时刻(写入后的10~15分钟,也可以手动执行optimize查询语句),ClickHouse会通过后台任务再将属于相同分区的多个目录合并成一个新的目录。已经存在的旧分区目录并不会立即被删除,而是在之后的某个时刻通过后台任务被删除(默认8分钟)。但是旧的分区目录已不再是激活状态(active=0),所以在数据查询时,它们会被自动过滤掉。

手动触发合并操作:

OPTIMIZE TABLE ***** FINAL;

合并后的分区目录:

PartitionID_合并前最小块号_合并前最大块号_(原有最大Level+1)

例如:201905_1_2_1和201905_5_5_0合并后:201905_1_5_2

分区名称变化如下图所示:

在这里插入图片描述

索引
一级索引

主键去重原理:

索引主键必须是Order By排序字段的子集。

按照Key键进行排序,相同的Key键只保留最新一条记录。默认情况下主键(PRIMARYKEY)与排序键相同。

MergeTree的主键使用PRIMARY KEY定义,待主键定义之后,MergeTree会依据index_granularity间隔(默认8192行),为数据表生成一级索引并保存至primary.idx文件内,索引数据按照PRIMARY KEY排序。

先排序,再按照指定行进行切分,索引文件单独存放。

二级索引

参考一级索引,多个索引字段匹配的key值存为一个索引。

MarkRange与索引编号对应,使用start和end两个属性表示其区间范围。通过与start及end对应的索引编号的取值,即能够得到它所对应的数值区间。而数值区间表示了此MarkRange包含的数据范围。

压缩数据块

核心概念:

  • 压缩算法
  • 文件结构
  1. 数据是经过压缩的,目前支持LZ4、ZSTD、Multiple和Delta几种算法,默认使用LZ4算法;
  2. 数据会事先依照ORDER BY的声明排序;
  3. 数据是以压缩数据块的形式被组织并写入.bin文件中的。
文件块的结构

一个压缩数据块由头信息和压缩数据两部分组成。头信息固定使用9位字节表示,具体由1个UInt8(1字节)整型和2个UInt32(4字节)整型组成,分别代表使用的压缩算法类型、压缩后的数据大小和压缩前的数据大小

例如:0x821200065536

0x82是压缩算法:LZ4的编码

12000:压缩后字节大小

65536:压缩前字节大小

Block合并的判断依据
  • 每次取8192行,size<64KB,等下一批次数据累加(还是8192行);
  • 64KB<=size<=1MB,直接生成一个压缩块;
  • size> 1MB,首先按照1MB大小截断并生成下一个压缩数据块,剩余数据继续依照上述规则执行。
数据标记

数据标记(.mrk)文件主要用于匹配索引文件和数据块文件。

一行标记数据使用一个元组表示,元组内包含两个整型数值的偏移量信息。它们分别表示在此段数据区间内,在对应的.bin压缩文件中,压缩数据块的起始偏移量;以及将该数据压缩块解压后,其未压缩数据的起始偏移量。

执行过程

盘点完数据的存储方式,本节梳理下MergeTree数据在写入和查询过程中如何快速定位到数据位置,并且读取数据,完成计算过程。

写入过程
  1. 生成分区目录,伴随着每一批数据的写入,都会生成一个新的分区目录。在后续的某一时刻,属于相同分区的目录会依照规则合并到一起;
  2. 按照index_granularity索引粒度,会分别生成primary.idx一级索引(如果声明了二级索引,还会创建二级索引文件);
  3. 生成每一个列字段的.mrk数据标记;
  4. 生成每一个列字段的.bin压缩数据文件(二进制)。

索引和标记区间是对齐的,而标记与压缩块则根据区间数据大小的不同,会生成多对一、一对一和一对多三种关系。对应关系依据如下:

  • 多对一:size < 64KB
  • 一对一:64KB<=size<=1MB
  • 一对多:size>1MB
查询过程

数据查询的本质,可以看作一个不断减小数据范围的过程。在最理想的情况下,MergeTree首先可以依次借助分区索引、一级索引和二级索引,将数据扫描范围缩至最小。然后再借助数据标记,将需要解压与计算的数据范围缩至最小。

MergeTree派生引擎

整个MergeTree家族中,除了基础表引擎MergeTree之外,还包括MergeTree的派生引擎。

单副本派生引擎

常用的表引擎还有ReplacingMergeTree、SummingMergeTree、AggregatingMergeTree、CollapsingMergeTree和VersionedCollapsingMergeTree等。每一种合并树的变种,在继承了基础MergeTree的能力之后,又增加了独有的特性。子引擎包含MergeTree的压缩数据块,列式存储,数据标记等特性。

它们的所有特殊逻辑,都是在触发合并的过程中被激活的。

例如:

  • ReplacingMergeTree:合并时,相同key值只保留一个;

  • SummingMergeTree:相同Key值,自动做Sum求和操作;

派生继承关系如下图:
在这里插入图片描述

多副本派生引擎

ReplicatedMergeTree系列:如果给合并树系列的表引擎加上Replicated前缀,又会得到一组支持数据副本的表引擎,例如ReplicatedMergeTree。主要作用是实现表的自动数据副本属性,和分布式表、数据副本等概念结合使用。和各类MergeTree的派生引擎组合关系如下图所示:

在这里插入图片描述

其他引擎

其他类型的引擎包括Kafka,HDFS、Mysql等,暂时不在本次讨论范围。

注:本博客是《Clickhouse原理解析与应用实践》MergeTree引擎章节笔记整理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值