本文主要介绍一下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
:数据块,包含Head
和CompressedData
Head
:包含CompressionMethod
、CompressedSize
、UncompressedSize
三部分,其中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值都会记录在该二进制文件中