关于列式数据库:MergeTree-创建方式和存储结构

44次阅读

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

创立形式和存储构造

Mergetree 在写入数据时,数据总会以数据片段的模式写入磁盘,为了防止片段过多,ClickHouse 会通过后盾线程,定期合并这些数据片段,属于雷同分区的数据片段会被合并成一个新的片段,正式合并树名称的由来。

创立形式

CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
    name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
    ...
    INDEX index_name1 expr1 TYPE type1(...) GRANULARITY value1,
    INDEX index_name2 expr2 TYPE type2(...) GRANULARITY value2
) ENGINE = MergeTree()
[PARTITION BY expr]
[ORDER BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]

主要参数:

PARTITION BY

分区键,不申明分区键,则会默认生成一个名为 all 的分区。

ORDER BY

必填,排序键,默认状况下主键与排序键雷同。

PRIMARY KEY

会依据主键字段生成一级索引,用于减速查问,可不申明,默认是 ORDER BY 定义的字段。

SAMPLE BY

抽样表达式,申明数据以何种规范进行采样,如果应用此配置,必须子主键的配置中也申明同样的表达式。
ORDER BY (CounterID,intHash32(UserID))
SAMPLE BY intHash32(UserID)

SETTINGS

index_granularity: 索引粒度,默认 8192,也就每隔 8192 行才生成一条索引
enable_mixed_granularity_parts: 是否开启自适应索引距离性能,默认开启
index_granularity_bytes: 索引粒度,依据每一批次写入数据的大小,动静划分距离大小,默认 10M(10*1024*1024)

存储构造

创立测试表

CREATE TABLE test.part_v1
(
    `ID` String,
    `URL` String,
    `age` UInt8 DEFAULT 0,
    `EventTime` Date
)
ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(EventTime)
ORDER BY ID
SETTINGS index_granularity = 8192

插入数据

insert into test.part_v1 values
('A001', 'www.test1.com', 1,'2020-08-01')
('A001', 'www.test1.com', 1,'2020-08-02')
('A002', 'www.test1.com', 1,'2020-08-03');

查看目录构造

[root@test 20200801_12_12_0]# ll
总用量 56
-rw-r----- 1 clickhouse clickhouse  29 8 月  18 15:56 age.bin
-rw-r----- 1 clickhouse clickhouse  48 8 月  18 15:56 age.mrk2
-rw-r----- 1 clickhouse clickhouse 456 8 月  18 15:56 checksums.txt
-rw-r----- 1 clickhouse clickhouse  91 8 月  18 15:56 columns.txt
-rw-r----- 1 clickhouse clickhouse   1 8 月  18 15:56 count.txt
-rw-r----- 1 clickhouse clickhouse  32 8 月  18 15:56 EventTime.bin
-rw-r----- 1 clickhouse clickhouse  48 8 月  18 15:56 EventTime.mrk2
-rw-r----- 1 clickhouse clickhouse  42 8 月  18 15:56 ID.bin
-rw-r----- 1 clickhouse clickhouse  48 8 月  18 15:56 ID.mrk2
-rw-r----- 1 clickhouse clickhouse   4 8 月  18 15:56 minmax_EventTime.idx
-rw-r----- 1 clickhouse clickhouse   4 8 月  18 15:56 partition.dat
-rw-r----- 1 clickhouse clickhouse  10 8 月  18 15:56 primary.idx
-rw-r----- 1 clickhouse clickhouse  49 8 月  18 15:56 URL.bin
-rw-r----- 1 clickhouse clickhouse  48 8 月  18 15:56 URL.mrk2
[root@test 20200801_12_12_0]# pwd
/var/lib/clickhouse/data/test/part_v1/20200801_12_12_0

目录档次:数据库名 > 数据表名 > 分区目录 > 分区下具体文件
20200801_12_12_0 是分区名

.txt 是明文存储,.bin/.dex/.mrk 二进制存储

  • partition.dat: 分区信息
  • checksum.txt: 数据校验信息
  • columns.txt: 列信息
  • count.txt: 计数信息
  • primary.idx: 一级索引信息,用于存储稠密索引信息
  • [column].bin: 存储某一列的信息,默认应用 lz4 压缩算法存储
  • [column].mrk: 列字段标记问题,保留.bin 文件中数据的偏移量信息
  • [column].mrk2: 如果定义了自适应索引,则会呈现该文件,作用和.mrk 文件一样
  • partition.dat、minmax_[column].idx: 定义了分区键,会呈现这二个文件,partition 存储以后分区下分区表达式最终生成的值,minmax_[column].idx 记录以后分区下对应原始数据的最小最大值
  • skp_idx_[Column].idx 与 skp_idx_[Column].mrk: 二级索引信息

数据分区

数据的分区规定

分区规定由分区 ID 决定,,分区 ID 生成规定有四种逻辑

  • 不指定分区键:没有定义 PARTITION BY,分区 ID 默认 all
  • 应用整型:间接按该整型的字符串模式输入,做为分区 ID
  • 应用日期类型:分区键时日期类型,或者能够转化成日期类型,比方用 today 转化,YYYYMMDD 格局按天分区,YYYYMM 按月分区等
  • 应用其余类型:String、Float 类型等,通过 128 位的 Hash 算法取其 Hash 值作为分区 ID

数据进行分区存储,在查问时能够疾速定位数据地位

分区目录的命名规定

分区命名规定,对于 20200801_1_1_0
PartitionID_MinBlockNum_MaxBlockNum_Level

  • PartitionID: 分区 ID,20200801 就是分区 ID
  • MinBlockNum、MaxBlockNum: 最小分区块编号和最大分区块编号,BlockNum 是整型的自增长编号,从 1 开始,新创建一个分区目录时,会 +1,新创建的分区 MinBlockNum=MaxBlockNum
  • Level:合并的层级,被合并的次数

分区目录的合并过程

每次数据 insert 写入,都会生成新的分区目录,在之后的某个时刻(写入后的 10-15 分钟,也能够手动执行 optimize 强制合并)会通过后台任务再将属于雷同分区的多个目录合并成一个新的目录,曾经存在的目录通过后台任务删除(默认 8 分钟)。

合并之后新目录名规定:

  • MinBlockNum:取同一分区内所有目录中最小的 MinBlockNum 值
  • MaxBlockNum:取同一分区内所有目录中最大的 MaxBlockNum 值
  • Level:取同以分区内最大 Level 值并 +1

一级索引

一级索引也就是主键索引,通过 PRIMARY KEY/ORDER BY 定义
会写入 primary.idx 文件中

稠密索引和浓密索引的区别

稠密索引应用一个索引标记一大段时间,缩小了索引的数据量,使得 primary.idx 能够常驻内存,减速数据查问

数据索引的生成过程

PARTITION BYtoYYYYMM(EventDate)),所以 2014 年 3 月份的数据最终会被划分到同一个分区目录内。应用 CounterID 作为主键(ORDER BY CounterID),每距离 8192 行会生成一个主键索引保留到 primary.idx 文件中

压缩数据块

数据标记的生成规定

数据标记是连接一级索引和数据的桥梁

数据标记和索引区间是对齐的,均依照 index_granularity 的粒度距离。只需简略通过索引区间的下标编号就能够间接找到对应的数据标记。每一个列字段[Column].bin 文件都有一个与之对应的[Column].mrk 数据标记文件,用于记录数据在.bin 文件中的偏移量信息

一行标记数据应用一个元组示意,元组内蕴含两个整型数值的偏移量信息。对应的.bin 压缩文件中,压缩数据块的起始偏移量;以及将该数据压缩块解压后,其未压缩数据的起始偏移量
每一行标记数据都示意了一个片段的数据(默认 8192 行)在.bin 压缩文件中的读取地位信息。标记数据与一级索引数据不同,它并不能常驻内存,而是应用 LRU(最近起码应用)缓存策略放慢其取用速度。

分区、索引、标记和压缩数据的协同总结

写入过程

首先生成分区目录,属于雷同分区的目录会按照规定合并到一起
紧接着依照 index_granularity 索引粒度,会别离生成 primary.idx 一级索引(如果申明了二级索引,还会创立二级索引文件)、每一个列字段的.mrk 数据标记和.bin 压缩数据文件

查问过程

查问的实质,能够看作一个一直减小数据范畴的过程。在最现实的状况下,MergeTree 首先能够顺次借助分区索引、一级索引和二级索引,将数据扫描范畴缩至最小。而后再借助数据标记,将 须要解压与计算的数据范畴缩至最小

如果一条查问语句用不到索引会进行分区指标扫描,虽不能放大数据范畴,然而 MergeTree 依然可能借助数据标记,以多线程的模式同时读取多个压缩数据块,以晋升性能

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

每个压缩数据块的体积都被严格控制在 64KB~1MB。而一个距离(index_granularity)的数据,又只会产生一行数据标记,依据一个距离内数据的理论字节大小,数据标记和压缩数据块之间会产生三种不同的对应关系

多对一

多个数据标记对应一个压缩数据块,当一个距离(index_granularity)内的数据未压缩大小 size 小于 64KB 时,会呈现这种对应关系。

一对一

一个数据标记对应一个压缩数据块,当一个距离(index_granularity)内的数据未压缩大小 size 大于 64KB 小于 1M 时,会呈现这种对应关系。

一对多

一个数据标记对应多个压缩数据块,当一个距离(index_granularity)内的数据未压缩大小 size 大于 1M 时,会呈现这种对应关系。

二级索引

二级索引又称跳数索引,由数据的聚合信息构建而成,依据索引类型的不同,其聚合信息的内容也不同。
须要在 CREATE 语句内定义,定义了跳数索引会额定生成相应的索引文件后标记文件
skp_idx_[Column].idx 和 skp_idx_[Column].mrk

1. 定义表构造

CREATE TABLE test.part_v2
(
    `ID` String,
    `URL` String,
    `age` UInt8 DEFAULT 0,
    `EventTime` Date
)
ENGINE = MergeTree()
    PARTITION BY toYYYYMMDD(EventTime)
    ORDER BY ID
    SETTINGS index_granularity = 8192

查看文件目录

总用量 4
drwxr-x--- 2 clickhouse clickhouse 6 8 月  20 18:54 detached
-rw-r----- 1 clickhouse clickhouse 1 8 月  20 18:54 format_version.txt

参考资料

《ClickHouse 原理解析与利用实际》

正文完
 0