目录
概述
ClickHouse是列存的数据,每一列的数据都会单独存放到bin文件中。这些.bin文件,最终承载着数据的物理存储。
按列独立存储的设计优势显而易见:
- 可以更好地进行数据压缩(相同类型的数据放在一起,对压缩更加友好);
- 能够最小化数据扫描的范围。
那么本章我们来详细的看看bin文件里面是如何存储数据的。
压缩数据块
一个压缩数据块由头信息和压缩数据两部分组成。头信息固定使用9位字节表示,具体由1个UInt8(1字节)整型和2个UInt32(4字节)整型组成,分别代表使用的压缩算法类型、压缩后的数据大小和压缩前的数据大小。如下图所示
通过上图可知,.bin压缩文件是由多个压缩数据块组成的,而每个压缩数据块的头信息则是基于CompressionMethod_CompressedSize_UncompressedSize公式生成的。
每个压缩数据块的体积,按照其压缩前的数据字节大小,都被严格控制在64KB~1MB,其上下限分别由min_compress_block_size(默认65536)与max_compress_block_size(默认1048576)参数指定。而一个压缩数据块最终的大小,则和一个间隔(index_granularity)内数据的实际大小相关。可以通过系统表来查看相应的配置信息如下所示。
select * from system.settings where name like '%_compress_block_size%'
压缩规则及流程
MergeTree在数据具体的写入过程中,会依照索引粒度(默认情况下,每次取8192行),按批次获取数据并进行处理。如果把一批数据的未压缩大小设为size,则整个写入过程遵循以下规则:
- 单个批次数据size<64KB :如果单个批次数据小于64KB,则继续获取下一批数据,直至累积到size>=64KB时,生成下一个压缩数据块。
- 单个批次数据64KB<=size<=1MB :如果单个批次数据大小恰好在64KB与1MB之间,则直接生成下一个压缩数据块。
- 单个批次数据size>1MB :如果单个批次数据直接超过1MB,则首先按照1MB大小截断并生成下一个压缩数据块。剩余数据继续依照上述规则执行。此时,会出现一个批次数据生成多个压缩数据块的情况。
通过上面的信息,我们可以知道一个.bin文件是由1至多个压缩数据块组成的,每个压缩块大小在64KB~1MB之间。多个压缩数据块之间,按照写入顺序首尾相接,紧密地排列在一起。
为什么要使用压缩数据块
在.bin文件中引入压缩数据块的目的至少有以下两点:
- 虽然数据被压缩后能够有效减少数据大小,降低存储空间并加速数据传输效率,但数据的压缩和解压动作,其本身也会带来额外的性能损耗。所以需要控制被压缩数据的大小,以求在性能损耗和压缩率之间寻求一种平衡。
- 在具体读取某一列数据时(.bin文件),首先需要将压缩数据加载到内存并解压,这样才能进行后续的数据处理。通过压缩数据块,可以在不读取整个.bin文件的情况下将读取粒度降低到压缩数据块级别,从而进一步缩小数据读取的范围。
结论
ClickHouse并不是一下子把所有的数据都直接写入到.bin文件的,而是经过了一番精心设计:
- 首先,数据是经过压缩的,目前支持LZ4、ZSTD、Multiple、Delta、T64、DoubleDelta和Gorilla几种算法,默认使用LZ4算法;
- 其次,数据会事先依照ORDER BY的声明排序;
- 最后,数据是以压缩数据块的形式被组织并写入.bin文件中的。
压缩数据块的设计可以有效的降低I/O读取的性能,并且极大的节省了设备的存储空间。
参考资料
-
<<ClickHouse原理解析与应用实践>>