底层存储,场景优化,自底向上的设计。
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呢?
在写这篇参会报告时,终于检索到了这个资料:
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就抛出了一个灵魂的质问:
做设计的原则,到底应该是 自顶向下 的去设计,还是应该 自下而上 的去设计 ?
在传统观念中,自然是 自顶向下的,做架构设计首先自然做的是顶层设计:
- 事先应该做高层次的抽象设计;
- 规划好各个模块的职责、切分的界面;
- 分配好工程结构、包结构,最好能再来一些设计图,等等。
但自顶向下的设计,往往并非最优的,而ClickHouse的设计,则采用了 自下而上,This is more optiaml。
ClickHouse的原型系统早在2008年就诞生了,在诞生之初,它并没有宏伟的规划。相反,它的目的很单纯,就是希望能以==最快的速度==进行GROUP BY查询和过滤。
从底,底在哪里呢?是硬件。
从硬件功能层面着手设计,在设计伊始,就至少需要想清楚这么几个问题:
- 我们将要使用的硬件水平是怎样的?包括CPU、内存、硬盘、网络等等;
- 在这样的硬件上,我们需要达到怎样的性能?包括延迟、吞吐量等等;
- 我们准备使用怎样的数据结构?包括String、HashTable、Vector等等;
- 选择的这些数据结构,在我们的硬件上会如何工作?
如果你能想清楚上面的问题,那么在动手实现功能之前,就已经能够计算出粗略的性能了。
基于将硬件功效最大化的目的,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.==
Every problem is a landscape
就Substring问题而言,即便是同一个问题,也会面临不同的场景,不同的状态,为了极尽性能,应选择不同的实现方式。
需要深入的,全面的了解所面临的问题,Every problem is a landscape,当全面了解之后,才能做得更好。
当然,clickhouse开发人员也难以穷尽所有的算法,Alexey Milovidov在会上提到Amos Bird(郑天祺)就为clickhouse的字符串合并函数提供了新的StringHashMap方案,这依赖于开源社区的力量。
针对特性场景的特殊优化。
类似的例子还有很多,例如去重计数uniqCombined函数,根据数据量的不同,会选择不同的算法:
当数据量较小的时候,会选择Array保存;
当数据量中等时候,则会选择HashSet;
而当数据量很大的时候,则使用HyperLogLog算法。
包括对于数据结构比较清晰的场景,会通过代码生成技术,实现循环展开,以减少循环次数。
还包括大家熟知的大杀器,向量化执行了。SIMD被广泛的应用于文本转换、数据过滤、数据解压和JSON转换等场景。利用寄存器暴力优化,相较于单纯的使用CPU而言,也算是一种降维打击了。
我的理解为,进一步的细化问题,细化场景。
总是用真实的数据测试
很多实验室做出来的数据库之所以不行,就是因为无法在真实的大规模数据上进行测试。而Clickhouse,人家有Yandex…
如何写出高效的代码
直接看slides。
作为开发者,需要更详细的了解项目工作的原理,了解其性能的优化点,知道what happdens,找到性能的问题,解决性能的问题。
还需要拥有一个能够持续验证、持续改进的机制,fast release life cycle and deployment。
最后用ClickHouse的口号来总结:
The ClickHouse Style:
as efficient as possible
as fast as possible
ClickHouse的魅力,就在于此。
读到这里的同学,强烈建议大家去看Alexey Milovidov的视频,虽然俄语口音有点重orz,在演说视频中有更细节的例子来描述优化的场景,相信你一定会受益匪浅。
参考资料
[2] 知乎回答
[3] 演说视频