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

Posted cfcz48

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 也有很长时间是在读取内存的。不过这个实验已经足够说明问题了。

分析

现代计算机体系结构中,将存储系统按照层次结构顺序排列,称为存储器层次结构(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 万次比较只需要(310^715)/(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

以上是关于Clickhouse系列-第二节-基本原理的主要内容,如果未能解决你的问题,请参考以下文章

第二十二节

(计算机组成原理)第六章总线-第二节:总线仲裁

(王道408考研操作系统)第三章内存管理-第二节1:虚拟内存管理基本概念

(王道408考研操作系统)第三章内存管理-第二节1:虚拟内存管理基本概念

(计算机组成原理)第四章指令系统-第二节3:数据寻址之偏移寻址(基址寻址变址寻址和相对寻址)

(计算机组成原理)第七章输入和输出系统-第二节2:外部设备之外存储器(磁盘的基本结构,磁盘阵列)