数据计算层面:
向量化执行+SIMD(单指令多数据流)
* ClickHouse实现了向量执行引擎(Vectorized execution engine),对内存中的列式数据,一个batch调用一次SIMD指令(而非每一行调用一次),不仅减少了函数调用次数、降低了cache miss,而且可以充分发挥SIMD指令的并行能力,大幅缩短了计算耗时。向量执行引擎,通常能够带来数倍的性能提升。
动态代码生成(Runtime Codegen)
* 在经典的数据库实现中,通常对表达式计算采用火山模型,也即将查询转换成一个个operator,比如HashJoin、Scan、IndexScan、Aggregation等。为了连接不同算子,operator之间采用统一的接口,比如open/next/close。在每个算子内部都实现了父类的这些虚函数,在分析场景中单条SQL要处理数据通常高达数亿行,虚函数的调用开销不再可以忽略不计。另外,在每个算子内部都要考虑多种变量,比如列类型、列的size、列的个数等,存在着大量的if-else分支判断导致CPU分支预测失效。 * ClickHouse实现了Expression级别的runtime codegen,动态地根据当前SQL直接生成代码,然后编译执行。如下图例子所示,对于Expression直接生成代码,不仅消除了大量的虚函数调用(即图中多个function pointer的调用),而且由于在运行时表达式的参数类型、个数等都是已知的,也消除了不必要的if-else分支判断。
多核并行处理+分布式处理
* ClickHouse将数据划分为多个partition,每个partition再进一步划分为多个index granularity,然后通过多个CPU核心分别处理其中的一部分来实现并行数据处理。在这种设计下,单条Query就能利用整机所有CPU。极致的并行处理能力,极大的降低了查询延时。 * 分布式计算,即多机器处理线性扩展
着眼硬件+高效的算法实现
# 比如 * ClickHouse设计时非常在于对CPU L3级别的缓存使用,因为一次L3 cache miss会带来70~100纳秒的延迟。这意味着,在单核CPU上,它会浪费4000万/每秒的运算;而在一个32线程的CPU上,则可能会浪费5亿/每秒的运算,别小看这些细节,一点一滴的将它们累加起来,数据是非常可观的。也正因为注意了这些细节,所以ClickHouse在基准查询中,能做到1.75亿/每秒的数据扫描性能。 * 针对不同的场景选择不同算法,例如去重计数uniqCombined函数,根据数据量的不同会选择不同的算法。当数据量较小的时候,会选择Array保存;当数据量中等时候,则会选择HashSet;而当数据量很大的时候,则使用HyperLogLog算法。
数据存储和写入层面:
列式存储+有序存储
主键索引+稀疏索引
数据分片
数据写入是Batch+Append+后台线程Merge