本文主要介绍一下ck数据目录都包含什么文件,以及文件内容都是什么,并着重介绍一些二进制文件的格式及内容。
首先按照如下规则创建表,用于后续数据的对照查询

#创建表
CREATE TABLE default.mt
(
    `a` Int32,
    `b` Int32,
    `c` Int32,
    INDEX `idx_c` (c) TYPE minmax GRANULARITY 1
)
ENGINE = MergeTree
PARTITION BY a 
ORDER BY b
SETTINGS index_granularity=3, index_granularity_bytes = 0;

#插入测试数据
insert into default.mt(a,b,c) values(3,10,4),(3,9,5),(3,8,6),(3,7,7),(3,6,8),(3,5,9),(3,4,10);

注意:设置index_granularity_bytes = 0取消自适应索引粒度,便于后续观察mrk文件结构

插入后看一下测试数据如下:

SELECT * FROM mt2

┌─a─┬──b─┬──c─┐
│ 3 │  4 │ 10 │
│ 3 │  5 │  9 │
│ 3 │  6 │  8 │
│ 3 │  7 │  7 │
│ 3 │  8 │  6 │
│ 3 │  9 │  5 │
│ 3 │ 10 │  4 │
└───┴────┴────┘

生成的数据文件如下:

[root@ck-chenzhen mt2]# pwd
/var/lib/clickhouse/data/default/mt
[root@ck mt]# tree
.
├── 3_1_1_0
│   ├── a.bin
│   ├── a.mrk
│   ├── b.bin
│   ├── b.mrk
│   ├── c.bin
│   ├── checksums.txt
│   ├── c.mrk
│   ├── columns.txt
│   ├── count.txt
│   ├── minmax_a.idx
│   ├── partition.dat
│   ├── primary.idx
│   ├── skp_idx_idx_c.idx
│   └── skp_idx_idx_c.mrk
├── detached
└── format_version.txt

一般的数据目录结构如下:

table_name                                 #表名
├─ partition_{index}            DIR        #分区目录
│  │  # 基础文件
│  ├─ checksums.txt             BIN        #各类文件的尺寸以及尺寸的散列
│  ├─ columns.txt               TXT        #列信息
│  ├─ count.txt                 TXT        #当前分区目录下数据总行数
│  ├─ primary.idx               BIN        #稀疏索引文件
│  ├─ {column}.bin              BIN        #经压缩的列数据文件,以字段名命名
│  ├─ {column}.mrk              BIN        #列字段标记文件
│  ├─ {column}.mrk2             BIN        #使用自适应索引间隔的标记文件
│  │
│  │  # 分区键文件
│  ├─ partition.dat             BIN        #当前分区表达式最终值
│  ├─ minmax_{column}.idx       BIN        #当前分区字段对应原始数据的最值
│  │ 
│  │  # 跳数索引文件
│  ├─ skp_idx_{column}.idx      BIN        #跳数索引文件
│  └─ skp_idx_{column}.mrk      BIN        #跳数索引表及文件
│
└─ partition_{index}            DIR        #分区目录

通过上面的标准结构可以分别对号入座

columns.txt

列信息文件,使用文本文件存储,用于保存分区下的列字段信息

[root@ck 3_1_1_0]# cat columns.txt
columns format version: 1
3 columns:
`a` Int32
`b` Int32
`c` Int32

记录了有多少个字段,及字段名字和类型

count.txt

计数文件,文本文件存储,用于记录当前数据分区目录下数据的总行数

[root@ck 3_1_1_0]# cat count.txt 
7

记录了该part中row的数量

primary.idx

一级索引文件,使用二进制格式存储

[root@ck 3_1_1_0]# od -An -i -w4 primary.idx
           4
           7
          10

主键索引,根据index_granularity索引粒度,每index_granularity行取一个主键列的值组合起来作为索引,例子中并没有设置PRIMARY KEY,clickhouse在这种情况下默认将ORDER BY的字段默认作为PRIMARY KEY,所以这里的primary.idx是根据b字段的数据进行固定间隔抽取的。例子中index_granularity=3,所以primary.idx中存的是4、7、10

{column}.mrk

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

以b.mrk为例

[root@ck 3_1_1_0]# od -An -l b.mrk
                    0                    0
                    0                   12
                    0                   24

mrk文件分为两列,第一个值对应bin文件中压缩后数据块的偏移量,第二个值对应bin文件解压后数据块的偏移量,单位均为字节。Int32为4字节,索引粒度为3,所以数据块的偏移量是0、12、24

直观点可以表示成下面规则:

primary.idx                     b.mrk2                       b.bin
┌───────────┐        ┌───────────────────────────┐        ┌───────────┐
│   b:4     │------->│ mark:0, offset:0, rows:3  │------->│     4     │
│           │        │                           │        │     5     │
│           │        │                           │        │     6     │
│   b:7     │------->│ mark:1, offset:12, rows:3 │------->│     7     │
│           │        │                           │        │     8     │
│           │        │                           │        │     9     │
│   b:10    │------->│ mark:2, offset:24, rows:3 │------->│     10    │
└───────────┘        └───────────────────────────┘        └───────────┘

所以primary.idx和mrk的行是一一对应的,

{column}.bin

数据文件,使用压缩格式存储,默认使用LZ4压缩格式,用于存储某一列的数据

以b.bin为例

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

┌────────────┐ ┌──────────────────────────────┐ ┌──────────────────────────────┐
│            │ │ Block 0                      │ │ Block 1                      │
│  Checksum  │ │ ┌────────┐┌────────────────┐ │ │ ┌────────┐┌────────────────┐ │
│            │ │ │ Head   ││ CompressedData │ │ │ │ Head   ││ CompressedData │ │  ...
│            │ │ └────┬───┘└────────────────┘ │ │ └────────┘└────────────────┘ │
└────────────┘ └──────┼───────────────────────┘ └──────────────────────────────┘
                      ↓                                                           
  ┌───────────────────┬────────────────────────┬───────────────────────┐
  │      UInt8        │         UInt32         │        UInt32         │
  ├───────────────────┼────────────────────────┼───────────────────────┤
  │ CompressionMethod │     CompressedSize     │     UncompressedSize  │
  └───────────────────┴────────────────────────┴───────────────────────┘

Checksum:该bin文件的校验值,16字节

Block:数据块,包含HeadCompressedData

Head:包含CompressionMethodCompressedSizeUncompressedSize三部分,其中CompressionMethod类型为UInt8占4字节,包含LZ4(0x82)、ZSTD(0x90)、Multipile(0x91)、Delta(0x92),CompressedSize类型为UInt32占4字节,UncompressedSize类型同样为UInt32占4字节。

CompressedData:压缩数据块,默认最小65535字节/64K,最大1048576字节/1M

查看bin文件内容可以使用官方的clickhouse-compressor,其余bin文件都可以用这个方法

[root@ck mt2]# clickhouse-compressor --decompress < 3_1_1_0/b.bin | od -An -i -w4
           4
           5
           6
           7
           8
           9
          10

还可以通过clickhouse-compressor查看b.bin的统计信息

[root@ck 3_1_1_0]# clickhouse-compressor --stat < b.bin
28	39

其中28表示压缩前数据大小,因为从4-10,每个数字占4字节,一共28字节,39表示压缩后数据大小,因为数据量太小,压缩也要有些必要的字节表示一些元信息,所以会比压缩前大,也可以再插入些数据再观察一下,如:

insert into default.mt(a,b,c) values(4,10,4),(4,9,5),(4,8,6),(4,7,7),(4,6,8),(4,5,9),(4,4,10),(4,4,11),(4,7,12),(4,4,13),(4,8,14),(4,6,15),(4,4,16),(4,6,17);

可以发现,压缩后是小的,如下:

[root@ck 4_2_2_0]# clickhouse-compressor --stat < b.bin
56	41

还回到原始数据的b.bin,看下二进制文件

[root@ck 3_1_1_0]# hexdump -C b.bin
00000000  e3 d5 c8 a9 76 46 66 84  9a 1d 24 3b e2 fb bd 2c  |....vFf...$;...,|
00000010  82 27 00 00 00 1c 00 00  00 f0 0d 04 00 00 00 05  |.'..............|
00000020  00 00 00 06 00 00 00 07  00 00 00 08 00 00 00 09  |................|
00000030  00 00 00 0a 00 00 00                              |.......|
00000037

其中:

第一行16个自己就是Checksum值;

第二行第一个字节0x82就是压缩算法,这里就是默认的LZ4,第三到第五字节就是CompressedSize,这里centos7中安装的ck,所以是小端模式,所以要吧27 00 00 00反过来看,即00 00 00 27,转换成10进制就是39,所以和上面的clickhouse-compressor中第二个数字39是一致的;继续向后看4个字节,反过来就是00 00 00 1c,即十进制的28,和上面的clickhouse-compressor中第二个数字28是一致的;再向后就涉及LZ4压缩的内容了

还可以通过clickhouse-compressor直接看数据的16进制存储

[root@ck 3_1_1_0]# clickhouse-compressor --decompress < b.bin | hexdump -C
00000000  04 00 00 00 05 00 00 00  06 00 00 00 07 00 00 00  |................|
00000010  08 00 00 00 09 00 00 00  0a 00 00 00              |............|
0000001c

可以看到从04 00 00 00到0a 00 00 00,反过来看也就是4-10

partition.dat

用于保存当前分区下分区表达式最终生成值

[root@ck 3_1_1_0]# od -An -l partition.dat
                    3

记录了该part的partition最终计算值,与3_1_1_0的3是一致的

minmax_a.idx

用于记录当前分区字段对应原始数据的最小值和最大值

[root@ck 3_1_1_0]# od -An -i minmax_a.idx 
           3           3
checksums.txt

校验文件,使用二进制存储,保存了各类文件的size大小和size的哈希值,用于快速校验文件的完整性和正确性

[root@ck 3_1_1_0]# od -An -c checksums.txt
   c   h   e   c   k   s   u   m   s       f   o   r   m   a   t
       v   e   r   s   i   o   n   :       4  \n   q 272 204   p
   [ 372 254 037 017   l 232 021 330 212   W 005 202   n 001  \0
  \0 226 001  \0  \0 360   5  \f 005   a   .   b   i   n   '   R
 006   K 230 270 317 324   :   ( 230 321 212   c   ^   W 307 001
 034 225 332 253 372 362 307 217 024 267 312 265   0   O 343 240
   > 005   a   .   m   r   k   0 351 370   x   * 260 214 231 305
 346   #   h   k 347 251 326 215  \0 005   b   A  \0 377 026   7
   R   7   G   L   - 361   a 035   * 242 356 306 262 275 304 032
 001 034 235  \0 300 242 221  \n 020   X 325   &   7 262   q 272
   N 177 005   b   A  \0 004 021   c   A  \0 377 025 206 215 371
 211   J 243   I   G 235 027   p   t   I 347   l 301 001 034   j
 003 253 355 232 342   t 310   g 365 357 313 306   j 370 265 005
   c   A  \0 003 361   r  \t   c   o   u   n   t   .   t   x   t
 001 246   % 315   - 241 273 272   B 265 006   c   p 240   " 240
   h  \0  \f   m   i   n   m   a   x   _   a   .   i   d   x  \b
   c 336 340  \a   f 022   )   t 335   ? 235 300 302 376   #   a
  \0  \r   p   a   r   t   i   t   i   o   n   .   d   a   t 004
 252 212   h 234 233 025 353 260 216 323 215 023 247 214 301 272
  \0  \v   p   r   i   m   a   r   y   .   i   d   x  \f   1 244
   t 271 002   ] 357   T 363 005 225 357   F 335 016   4  \0 021
   s   k   p   _   i   d   x 004  \0 020   c   b  \0 373 024   3
   Y 020   r 302 312 254 004 327 207 355 017   - 025 361 022 334
 001 030   ! 255 277 232   b 224 211 004 275 273   Y 300 263 213
 372   i   5  \0  \0   T 001 360 002 275   ]   e   A   9 313   -
   2 027   + 003   0   2 245   * 360  \0

从中能看到各个column的bin和mrk文件、primary.idx、minmax_a.idx、skp_idx_c.idx、skp_idx_c.mrk的checksum值都会记录在该二进制文件中