Clickhouse系列-第二节-基本原理

在正式开始clickhouse探秘前,我们先抛出一个问题:影响OLAP查询速度的是什么?是优秀的算法么?不可否认,算法对查询性能的影响非常大,但到了现阶段通用的算法基本上已经能够达到很高的性能了。因此,在现阶段,制约着大数据OLAP查询速度的已经不再是算法了。那么这个问题的答案是什么呢?实践是检验整理的唯一标准,我们来做个实验看一下。

实验

我们写一段代码来模拟数据库执行SELECT max(id) From tbl_a这句语句。如果不考虑前面的sql解析过程,可以简单抽象成两个步骤:

  1. 从磁盘中读取数据文件,载入内存
  2. 解析数据并找出最大的id
// 从磁盘将文件载入内存
long s = System.nanoTime();
List<String> lines = FileUtils.readLines(file,"utf8");
long e1 = System.nanoTime();
System.out.println("文件读取完成,耗时:"+((e1-s)/1e6)+" ms,开始找最大值");
// 解析并求出最大值
String maxId = lines.parallelStream()
         .map(e -> e.substring(0,e.indexOf(" ")).trim())
         .max(Comparator.comparingInt(Integer::parseInt)).orElse("未找到");
long e2 = System.nanoTime();
// 计算每个阶段的耗时
System.out.println("计算完成,耗时:"+((e2-e1)/1e6)+" ms");
System.out.println("程序运行完成,总耗时:"+((e2-s)/1e6)+" ms(不计算生成数据文件文件的时间)");

代码如上所示,使用下表的结构模拟数据表:

id姓名部门名称岗位名称
1Amy大数据部部门经理
2Tom大数据部部门经理
3John大数据部部门经理
表1 模拟表结构

使用3000万条数据作为测试,数据集大小为1.1G。最终执行结果为:

文件读取完成,耗时:4971.683747 ms
计算完成,耗时:432.261263 ms

可以看出,两者差距接近了11倍。并且,这个实验的第二步,其实CPU也有很长时间是在读取内存的。不过这个实验已经足够说明问题了。

很明显,现代OLAP需要想办法降低磁盘IO的时间。

简单分析

现代计算机体系结构中,将存储系统按照层次结构顺序排列,称为存储器层次结构(Memory Hierarchy)。由此,从磁盘上读取数据的时间远大于CPU计算的时间。可以做个粗略的计算对比:

假设一台2.4GHz的CPU+7200转的机械硬盘。那么CPU执行一条指令的时间是1/2.4G≈0.41ns。7200转机械硬盘的读取约为100MB每秒。也就是说CPU执行一条指令的时间硬盘理论上只能读取约0.041个字节。而实际上0.41ns都不足以使得磁盘开始转动。

再换种计算方法,3000万条记录,1.1G的大小,计算机从磁盘中载入这个文件需要约1126MB/100MB/S≈11.26秒。而计算机从3000万条记录上找出最大值,大约需要执行3000万次比较,每次比较约执行15条1机器指令,如果只计算执行指令的CPU时间,不考虑内存读写时间,执行3000万次比较只需要(3*10^7*15)/(2.4*10^9)≈0.019秒。两者相差接近600倍。由此可见,计算任务的大部分的时间其实都在IO上。

由此我们可以知道,CPU参与运算的时间远小于磁盘IO的时间。这种情况下,使用算法优化CPU执行的指令,会显得很不划算。

那么文章开头提出的问题的答案就呼之欲出了——降低IO来加速计算。因此,现代olap大部分都是选择降低磁盘IO来获得加速效果。

降低磁盘IO的手段

如表2所示,现代OLAP数据库一般使用两种手段降低磁盘IO时间对查询性能的影响。

手段原理代表数据库缺陷
分布式并行读取数据,降低单台服务器需要读取的数据量Hive(textfile),greenplum单独使用本质上依然需要读取所有数据,耗费资源
行变列(列存数据库)
行存数据库即使只需要某一列的数据,也必须读取所有数据(如定量中的例子)列存通过将每一列单独存储,做到按需读取hbase适合使用列比较单一的业务。
表2 降低磁盘IO时间的手段

分布式是大数据早期时代使用的方案,他的原理和思想非常简单粗暴,单机处理慢,我就分成N台服务器进行计算,每一台只需要计算一小部分,最后将结果汇总。例如最早期的Hive。这种架构的优点是简单粗暴,但也有明显的缺陷,就是虽然等待时间变少了,但是整体上看,将多台机器的计算时间汇总,总耗时是大于单机处理时间的。而且也很容易发生数据倾斜问题和增加网络传输时间。其实是浪费了资源。因此随着技术的发展,基本上大家都使用了分布式和行变列混用的架构。hive在后期也增加了列存引擎。

列存数据库的原理将每一列单独存储,降低每次载入的数据量。还是以定量分析节中的例子为例,数据文件data.bin大小为1.1G,载入时间4.9s。如果将每一列单独写入一个文件,那么如果只需要计算出最大id,那就只需要载入id.bin文件,这个文件约为247MB,理论上能将读取时间减少75%左右。同样,列存也有一个很大缺陷,考虑一个极端情况,如果查询语句非常复杂,从而用到了所有的列,那么列存也无法降低读取时间。所以,OLAP适合处理大宽表,列越多,其性能对比行式数据库的提升越大。

这里插个题外话,了解了列存数据库的原理后,读者要明确,将数据从行式数据库导入到列式数据库进行分析时,请一定记得将行式数据库的星型表转换成列式数据库的大宽表。否则性能提升有可能会不明显。在我的职业生涯中,我就遇到过数仓工程师将数据从MySQL导入adb后,直接进行查询,然后责怪adb性能为什么这么差!这也是为什么数仓模型要分层的原因。另外有些读者可能会有疑惑,为什么行式数据库不搞成大宽表?这里面的原因不在本系列的范畴内,后续笔者会另外写一篇单独的文章,为读者讲透这里面的原因,敬请期待,也可以在评论区说出你的想法。

降低内存IO的手段

相比较磁盘io,内存io的优化做的不多,能提升的空间也很有限,以第一节的例子为例,载入内存后CPU计算一共花费432ms,即使做到极致,也只能减少约400ms的时间。因此,OLAP在内存IO上的优化不大。

不过clickhouse也依然做了一些令人拍案叫绝的优化,同样会在后续章节详细解析。

clickhouse就是使用了列存的数据库,类似的还有后期的hive、greenplum。那么,同样都是列存数据库,凭什么clickhouse就能比这些数据库快呢?本系列的后续部分将继续探讨clickhouse的巧思。再次强调,clickhouse并没有提出新的计算理论,只是在列存的基础上进行了多项优化。从而提现了clickhouse工程师的令人惊叹而着迷的奇思妙想。

[1] 假设比较函数如下:

int compare(int a,int b){
    return a>=b?a:b;
}

以上C语言函数未经编译器优化生成的机器码大约是15条指令,实际上将其声明为inline并打开编译器优化后会生成的机器指令会更少。但该代码也已经足够说明问题了。生成的机器码如下:

    0x100e88f50 <+0>:  pushq  %rbp
    0x100e88f51 <+1>:  movq   %rsp, %rbp
    0x100e88f54 <+4>:  movl   %edi, -0x4(%rbp)
    0x100e88f57 <+7>:  movl   %esi, -0x8(%rbp)
->  0x100e88f5a <+10>: movl   -0x4(%rbp), %eax
    0x100e88f5d <+13>: cmpl   -0x8(%rbp), %eax
    0x100e88f60 <+16>: jl     0x100e88f71               ; <+33> at main.c:12:19
    0x100e88f66 <+22>: movl   -0x4(%rbp), %eax
    0x100e88f69 <+25>: movl   %eax, -0xc(%rbp)
    0x100e88f6c <+28>: jmp    0x100e88f77               ; <+39> at main.c
    0x100e88f71 <+33>: movl   -0x8(%rbp), %eax
    0x100e88f74 <+36>: movl   %eax, -0xc(%rbp)
    0x100e88f77 <+39>: movl   -0xc(%rbp), %eax
    0x100e88f7a <+42>: popq   %rbp
    0x100e88f7b <+43>: retq   

3,341条评论

  1. Howdy are using WordPress for your blog platform? I’m new to the blog world but I’m trying to get started and create my own. Do you need any html coding knowledge to make your own blog? Any help would be really appreciated!