CK为啥快捏?The secret of ClickHouse

底层存储,场景优化,自底向上的设计。

CK为啥快捏?The secret of ClickHouse

The Secrets of ClickHouse Performance Optimizations / How to write efficient code.

最近在做clickhouse和openLooKeng相关的项目,进一步接触了ClickHouse这个高效快速的列式数据库,为clickhouse超快的性能惊叹的同时,也产生了疑问,ClickHouse为什么这么快呢?

之前自己的理解是ClickHouse是列式数据库,但是列式数据库有很多,却没有达到ClickHouse的快速,mysql也有列式存储的引擎但为何应用却不如ClickHouse呢?

在写这篇参会报告时,终于检索到了这个资料:

9fecb5d9d952c95fd3f38e34962ded5c

PS: Amos bird是计算所博士,曾魔改ClickHouse作为图存储的数据库,遗憾的是网上只能找到他的报告,没有看到论文和代码。

开个玩笑~

在网上有很多关于ClickHouse为什么性能这么好的原因,比如[1]提到的12个原因:

1. 真正的面向列的DBMS

2. 数据压缩

3. 磁盘存储的数据

4. 多核并行处理

5. 在多个服务器上分布式处理

6. SQL支持

7. 向量化引擎

8. 实时数据更新

9. 索引

10. 支持在线查询

11. 支持近似计算

12. 数据复制和对数据完整性的支持

上面这些理由当然是完全正确的,也是clickhouse之所以快的原因,(虽然有些理由应该算是“特性”),但总感觉差点意思,直到看到[1]中提起,ClickHouse项目的创始人Alexey Milovidov在19年的中国大数据技术大会上有对clickhouse的优化进行了分享[3],并非从特性揭开,而是从设计角度揭开clickhouse性能好的原因。

下面文字部分引用于[1]

设计理念:自顶向下 vs 自底向上

开篇伊始,Alexey Milovidov就抛出了一个灵魂的质问:

做设计的原则,到底应该是 自顶向下 的去设计,还是应该 自下而上 的去设计 ?

在传统观念中,自然是 自顶向下的,做架构设计首先自然做的是顶层设计:

image-20201220163340247

  • 事先应该做高层次的抽象设计;
  • 规划好各个模块的职责、切分的界面;
  • 分配好工程结构、包结构,最好能再来一些设计图,等等。

但自顶向下的设计,往往并非最优的,而ClickHouse的设计,则采用了 自下而上,This is more optiaml。

image-20201220163315859

ClickHouse的原型系统早在2008年就诞生了,在诞生之初,它并没有宏伟的规划。相反,它的目的很单纯,就是希望能以==最快的速度==进行GROUP BY查询和过滤。

从底,底在哪里呢?是硬件。

image-20201220163502212

从硬件功能层面着手设计,在设计伊始,就至少需要想清楚这么几个问题:

  1. 我们将要使用的硬件水平是怎样的?包括CPU、内存、硬盘、网络等等;
  2. 在这样的硬件上,我们需要达到怎样的性能?包括延迟、吞吐量等等;
  3. 我们准备使用怎样的数据结构?包括String、HashTable、Vector等等;
  4. 选择的这些数据结构,在我们的硬件上会如何工作?

image-20201220163629568

如果你能想清楚上面的问题,那么在动手实现功能之前,就已经能够计算出粗略的性能了。

基于将硬件功效最大化的目的,ClickHouse会在内存中进行GROUP BY,并且使用HashTable装载数据。于此同时,他们非常在意CPU L3级别的缓存,因为一次L3 cache miss会带来70~100纳秒的延迟。这意味着,在单核CPU上,它会浪费4000万/每秒的运算;而在一个32线程的CPU上,则可能会浪费5亿/每秒的运算。

所以别小看这些细节,一点一滴的将它们累加起来,数据是非常可观的。也正因为注意了这些细节,所以ClickHouse在基准查询中,能做到1.75亿/每秒的数据扫描性能。

从一点一滴积累优势进行优化,让我想起了hikariCP。

设计理念:算法在前,抽象在后

If you need maximum performance - then interfaces in the code are determined by algorithms.

在ClickHouse的底层实现中,经常会面对这些场景:字符串子串查询;数组排序;使用HashTable等。

如何才能在实现性能的最大化呢?算法的选择是重中之重!!!

以字符串为例,有一本专门讲解字符串搜索的书,叫做 “Handbook of Exact String Matching Algorithms“,这本书列举了35种常见的字符串搜索算法,你猜ClickHouse使用了其中的哪一种?

一种都没用!! 为什么?因为性能不够快。

==You can always do better if you know your task better.==

image-20201220164115993

Every problem is a landscape

就Substring问题而言,即便是同一个问题,也会面临不同的场景,不同的状态,为了极尽性能,应选择不同的实现方式。

image-20201220164318904

需要深入的,全面的了解所面临的问题,Every problem is a landscape,当全面了解之后,才能做得更好。

当然,clickhouse开发人员也难以穷尽所有的算法,Alexey Milovidov在会上提到Amos Bird(郑天祺)就为clickhouse的字符串合并函数提供了新的StringHashMap方案,这依赖于开源社区的力量。

针对特性场景的特殊优化。

image-20201220165225100

类似的例子还有很多,例如去重计数uniqCombined函数,根据数据量的不同,会选择不同的算法:

当数据量较小的时候,会选择Array保存;

当数据量中等时候,则会选择HashSet;

而当数据量很大的时候,则使用HyperLogLog算法。

包括对于数据结构比较清晰的场景,会通过代码生成技术,实现循环展开,以减少循环次数。

还包括大家熟知的大杀器,向量化执行了。SIMD被广泛的应用于文本转换、数据过滤、数据解压和JSON转换等场景。利用寄存器暴力优化,相较于单纯的使用CPU而言,也算是一种降维打击了。

我的理解为,进一步的细化问题,细化场景。

总是用真实的数据测试

image-20201220165612898

很多实验室做出来的数据库之所以不行,就是因为无法在真实的大规模数据上进行测试。而Clickhouse,人家有Yandex…

image-20201220165714487

如何写出高效的代码

直接看slides。

作为开发者,需要更详细的了解项目工作的原理,了解其性能的优化点,知道what happdens,找到性能的问题,解决性能的问题。

还需要拥有一个能够持续验证、持续改进的机制,fast release life cycle and deployment。

image-20201220170048898

最后用ClickHouse的口号来总结:

The ClickHouse Style:

as efficient as possible

as fast as possible

ClickHouse的魅力,就在于此。

读到这里的同学,强烈建议大家去看Alexey Milovidov的视频,虽然俄语口音有点重orz,在演说视频中有更细节的例子来描述优化的场景,相信你一定会受益匪浅。

参考资料

[1] infoq为什么clickhouse这么快?

[2] 知乎回答

[3] 演说视频