数据存储
数据块 data part 表由按主键排序的数据片段(DATA PART)组成; 数据片段可以以 Wide 或 Compact 格式存储。在 Wide 格式下,每一列都会在文件系统中存储为单独的文件,在 Compact 格式下所有列都存储在一个文件中。数据存储格式由 min_bytes_for_wide_part 和 min_rows_for_wide_part 表引擎参数控制。如果数据片段中的字节数或行数少于相应的设置值,数据片段会以 Compact 格式存储,否则会以 Wide 格式存储。
数据块 data block 列存储文件的压缩单元。每个列存文件的Block包含若干个Granule,具体多少个Granule是由参数min_compress_block_size控制,每次列的Block中写完一个Granule的数据时,它会检查当前Block Size有没有达到设定值,如果达到则会把当前Block进行压缩然后写磁盘。
数据颗粒 每个数据片段被逻辑的分割成颗粒(granules)。颗粒是 ClickHouse 中进行数据查询时的最小不可分割数据集。ClickHouse 不会对行或值进行拆分,所以每个颗粒总是包含整数个行。每个颗粒的第一行由主键值标记;颗粒的大小通过表引擎参数 index_granularity 和 index_granularity_bytes 控制。取决于行的大小,颗粒的行数的在 [1, index_granularity] 范围中。如果单行的大小超过了 index_granularity_bytes 设置的值,那么一个颗粒的大小会超过 index_granularity_bytes。在这种情况下,颗粒的大小等于该行的大小。
索引间隔 index_granularity8192 每隔8192条数据生成一条索引,决定了每个数据颗粒的大小
索引文件 ClickHouse 会为每个数据片段创建一个索引文件来存储这些标记。对于每列,无论它是否包含在主键当中,ClickHouse 都会存储类似标记。这些标记让你可以在列文件中直接找到数据。
主键索引 对应的文件是primary.idx,与传统数据库不同, 主键索引是可以相同的。主键索引存储的是主键值和block, 且数据是按照主键进行排序的。 若查询条件中含有主键值, 则会先根据主键索引得到可能的granule范围,再根据mk标记文件中的偏移量确定数据。
分区键索引对应的文件是minmax_分区名.idx。记录当前分区下分区字段对应原始数据的最小和最大值。用于查询时迅速排除不需要的分区。
文件路径
/var/lib/clickhouse/data/nxdb4/nx_table_merge1 nxdb4是数据库名,nx_table_merge1是表名,表名目录下面是分区目录;余下各类数据文件(primary.idx、[Column].mrk、[Column]. bin等)都 是以分区目录的形式被组织存放的,属于相同分区的数据,最终会被合并到同一个分区目录,而不同分 区的数据,永远不会被合并在一起
数据写入过程
伴随每次写入数据(insert),MergeTree都会生成一批新的分区目录(即使不同批次写入的数据属于相同分区,也会生成不同的分区目录)。在写入后的某个时刻,ClickHouse会通过后台任务再将属于相同分区的多个目录合并成一个新目录。已经存在的旧分区并不会立即删除,而是在之后的某个时刻通过后台任务删除(默认8分钟)。
MergeTree表的写入链路是一个极端的batch load过程,Data Part不支持单条的append insert。每次batch insert都会生成一个新的MergeTree Data Part。如果用户单次insert一条记录,那就会为那一条记录生成一个独立的Data Part,这必然是无法接受的。一般我们使用MergeTree表引擎的时候,需要在客户端做聚合进行batch写入或者在MergeTree表的基础上创建Distributed表来代理MergeTree表的写入和查询,Distributed表默认会缓存用户的写入数据,超过一定时间或者数据量再异步转发给MergeTree表。MergeTree存储引擎对数据实时可见要求非常高的场景是不太友好的。
数据查询
分为两步:索引检索、数据扫描;索引检索部分对每个MergeTree Data Part是串行执行,但Data Part之间的检索没有任何关联。而在数据扫描部分中最底层的列存扫描是多所有Data Part并行执行,各Data Part的列存扫描之间也没有任何关联。
先抽取出查询中的分区键和主键条件的KeyCondition;索引检索的过程中首先会用分区键KeyCondition裁剪掉不相关的数据分区,然后用主键索引挑选出粗糙的Mark Range,最后再用Skipping Index过滤主键索引产生的Mark Range;
数据扫描的三种模式:final、sorted、normal
final模式是针对于SummingMergeTree、CollapsingMergeTree,提供最终视图。原理不明白。。。
sorted模式,基于全局排序。。。用法、原理不明白
normal模式,
并行扫描:传统的计算引擎在数据扫描部分的并发度大多和存储文件数绑定在一起,MergeTree Data Part并行扫描是一个基础能力。ClickHouse在MergeTree Data Part并行的基础上还增加了Mark Range并行,用户可以任意设定数据扫描过程中的并行度,每个扫描线程分配到的是Mark Range In Data Part粒度的任务,同时多个扫描线程之间还共享了Mark Range Task Pool,这样可以避免在存储扫描中的长尾问题。
数据Cache:MergeTree的查询链路中涉及到的数据有不同级别的缓存设计。主键索引和分区键索引在load Data Part的过程中被加载到内存,Mark文件和列存文件有对应的MarkCache和UncompressedCache,MarkCache直接缓存了Mark文件中的binary内容,而UncompressedCache中缓存的是解压后的Block数据。
SIMD反序列化:部分列类型的反序列化过程中采用了手写的sse指令加速,在数据命中UncompressedCache的情况下会有一些效果。
PreWhere过滤:ClickHouse的语法支持了额外的PreWhere过滤条件,它会先于Where条件进行判断。当用户在sql的filter条件中加上PreWhere过滤条件时,存储扫描会分两阶段进行,先读取PreWhere条件中依赖的列值,然后计算每一行是否符合条件。相当于在Mark Range的基础上进一步缩小扫描范围,PreWhere列扫描计算过后,ClickHouse会调整每个Mark对应的Granule中具体要扫描的行数,相当于可以丢弃Granule头尾的一部分行。