其他分享
首页 > 其他分享> > clickhouse 为什么如此快及优化

clickhouse 为什么如此快及优化

作者:互联网

一、clickhouse 为什么如此快

1)优秀的代码,对性能的极致追求

clickhouse 是 CPP 编写的,代码中大量使用了 CPP 最新的特性来对查询进行加速。

2)优秀的执行引擎以及存储引擎

clickhouse 是基于列式存储的,使用了向量化的执行引擎,利用 SIMD 指令进行处理加速,同时使用 LLVM 加快函数编译执行,当然了 Presto 也大量的使用了这样的特性。

3)稀疏索引

相比于传统基于 HDFS 的 OLAP 引擎,clickhouse 不仅有基于分区的过滤,还有基于列级别的稀疏索引,这样在进行条件查询的时候可以过滤到很多不需要扫描的块,这样对提升查询速度是很有帮助的。

4)存储执行耦合

存储和执行分离是一个趋势,但是存储和执行耦合也是有优势的,避免了网络的开销,CPU 的极致压榨加上 SSD 的加持,每秒的数据传输对于网络带宽的压力是非常大的,耦合部署可以避免该问题。

5)数据存储在 SSD,极高的 iops。

4、clickhouse 的 insert 和 select

1)clickhouse 如何完成一次完整的 select

这里有个概念需要澄清一下,clickhouse 的表分为两种,一种是本地表另一种是分布式表。本地表是实际存储数据的而分布式表是一个逻辑上的表,不存储数据的只是做一个路由使用,一般在查询的时候都是直接使用分布式表,分布式表引擎会将我们的查询请求路由本地表进行查询,然后进行汇总最终返回给用户。

2)索引在查询中的使用

索引是 clickhouse 查询速度比较快的一个重要原因,正是因为有索引可以避免不必要的数据的扫描和处理。传统基于 hdfs 的 olap 引擎都是不支持索引的,基本的数据过滤只能支持分区进行过滤,这样会扫描处理很多不必要的数据。

clickhouse 不仅支持分区的过滤也支持列级别的稀疏索引。clickhouse 的基础索引是使用了和 kafka 一样的稀疏索引,索引粒度默认是 8192,即每 8192 条数据进行一次记录,这样对于 1 亿的数据只需要记录 12207 条记录,这样可以很好的节约空间。

二分查找+遍历也可以快速的索引到指定的数据,当然相对于稠密索引,肯定会有一定的性能损失,但是在大数据量的场景下,使用稠密索引对存储也是有压力的。

 

下面我们通过举例看下索引在 clickhouse 的一次 select 中的应用,该表的排序情况为 order by CounterID, Date 第一排序字段为 CounterID,第二排序字段为 Date,即先按照 CounterID 进行排序,如果 CounterID 相同再按照 Date 进行排序。

CounterID 是第一索引列,可以直接定位到 CounterId=’a’的数据是在[0,3]数据块中。

Date 为第二索引列,索引起来有点费劲,过滤效果还不是特别的好,Date=’3’的数据定位在[2,10]数据块中。

第一索引 + 第二索引同时过滤,[0,3] 和 [2,10]的交集,所以为[2,3]数据块中。

对于这样没有索引字段的查询就需要直接扫描全部的数据块[0,10]。

3)clickhouse 如何完成一次插入

clickhouse 的插入是基于 Batch 的,它不能够像传统的 mysql 那样频繁的单条记录插入,批次的大小从几千到几十万不等,需要和列的数量以及数据的特性一起考虑,clickhouse 的写入和 Hbase 的写入有点”像”(类 LSM-Tree),主要区别有:

clickhouse 写入的时候是直接落盘的, 在落盘之前会对数据进行排序以及必要的拆分(如不同分区的数据会拆分成多个文件夹),如果使用的是 ReplicatedMergeTree 引擎还需要与 zookeeper 进行交互,最终会有线程在后台把数据(文件夹)进行合并(merge),将小文件夹合并生成大文件夹方便查询的时候进行读取(小文件会影响查询性能)。

二、关于集群的搭建

1)单副本

缺点:

2)多副本

多副本可以完美的解决单副本的所有的问题,多副本有 2 个解决方案:

两种方案的优缺点:

三、常见的引擎(MergeTree 家族)

1)(Replicated)MergeTree

该引擎为最简单的引擎,存储最原始数据不做任何的预计算,任何在该引擎上的 select 语句都是在原始数据上进行操作的,常规场景使用最为广泛,其他引擎都是该引擎的一个变种。

2)(Replicated)SummingMergeTree

该引擎拥有“预计算(加法)”的功能。

实现原理:在 merge 阶段把数据加起来(对于需要加的列需要在建表的时候进行指定),对于不可加的列,会取一个最先出现的值。

3)(Replicated)ReplacingMergeTree

该引擎拥有“处理重复数据”的功能。

使用场景:“最新值”,“实时数据”。

4)(Replicated)AggregatingMergeTree

该引擎拥有“预聚合”的功能。

使用场景:配合”物化视图”来一起使用,拥有毫秒级计算 UV 和 PV 的能力。

5)(Replicated)CollapsingMergeTree

该引擎和 ReplacingMergeTree 的功能有点类似,就是通过一个 sign 位去除重复数据的。

需要注意的是,上述所有拥有"预聚合"能力的引擎都在"Merge"过程中实现的,所以在表上进行查询的时候 SQL 是需要进行特殊处理的。

如 SummingMergeTree 引擎需要自己 sum(), ReplacingMergeTree 引擎需要使用时间+版本进行 order by + limit 来取到最新的值,由于数据做了预处理,数据量已经减少了很多,所以查询速度相对会快非常多。

四、最佳实践

1)实时写入使用本地表,不要使用分布式表

分布式表引擎会帮我们将数据自动路由到健康的数据表进行数据的存储,所以使用分布式表相对来说比较简单,对于 Producer 不需要有太多的考虑,但是分布式表有些致命的缺点。

2)推荐使用(*)MergeTree 引擎,该引擎是 clickhouse 最核心的组件,也是社区优化的重点

数据有保障,查询有保障,升级无感知。

3)谨慎使用 on cluster 的 SQL

使用该类型 SQL hang 住的案例不少,我们也有遇到,可以直接写个脚本直接操作集群的每台进行处理。

五、常见参数配置推荐

1)max_concurrent_queries

最大并发处理的请求数(包含 select,insert 等),默认值 100,推荐 150(不够再加),在我们的集群中出现过”max concurrent queries”的问题。

2)max_bytes_before_external_sort

当 order by 已使用 max_bytes_before_external_sort 内存就进行溢写磁盘(基于磁盘排序),如果不设置该值,那么当内存不够时直接抛错,设置了该值 order by 可以正常完成,但是速度相对存内存来说肯定要慢点(实测慢的非常多,无法接受)。

3)background_pool_size

后台线程池的大小,merge 线程就是在该线程池中执行,当然该线程池不仅仅是给 merge 线程用的,默认值 16,推荐 32 提升 merge 的速度(CPU 允许的前提下)。

4)max_memory_usage

单个 SQL 在单台机器最大内存使用量,该值可以设置的比较大,这样可以提升集群查询的上限。

5)max_memory_usage_for_all_queries

单机最大的内存使用量可以设置略小于机器的物理内存(留一点内操作系统)。

6)max_bytes_before_external_group_by

在进行 group by 的时候,内存使用量已经达到了 max_bytes_before_external_group_by 的时候就进行写磁盘(基于磁盘的 group by 相对于基于磁盘的 order by 性能损耗要好很多的),一般 max_bytes_before_external_group_by 设置为 max_memory_usage / 2,原因是在 clickhouse 中聚合分两个阶段:

这些内存参数强烈推荐配置上,增强集群的稳定性避免在使用过程中出现莫名其妙的异常。

六、

9、那些年我们遇到过的问题

1)Too many parts(304). Merges are processing significantly slower than inserts

相信很多同学在刚开始使用 clickhouse 的时候都有遇到过该异常,出现异常的原因是因为 MergeTree 的 merge 的速度跟不上目录生成的速度, 数据目录越来越多就会抛出这个异常, 所以一般情况下遇到这个异常,降低一下插入频次就 ok 了,单纯调整 background_pool_size 的大小是治标不治本的。

我们的场景

我们的插入速度是严格按照官方文档上面的推荐”每秒不超过 1 次的 insert request”,但是有个插入程序在运行一段时间以后抛出了该异常,很奇怪。

问题排查

排查发现失败的这个表的数据有一个特性,它虽然是实时数据但是数据的 eventTime 是最近一周内的任何时间点,我们的表又是按照 day + hour 组合分区的那么在极限情况下,我们的一个插入请求会涉及 7*24 分区的数据,也就是我们一次插入会在磁盘上生成 168 个数据目录(文件夹),文件夹的生成速度太快,merge 速度跟不上了,所以官方文档的上每秒不超过 1 个插入请求,更准确的说是每秒不超过 1 个数据目录。

case study

分区字段的设置要慎重考虑,如果每次插入涉及的分区太多,那么不仅容易出现上面的异常,同时在插入的时候也比较耗时,原因是每个数据目录都需要和 zookeeper 进行交互。

2)DB::NetException: Connection reset by peer, while reading from socket xxx

查询过程中 clickhouse-server 进程挂掉。

问题排查

排查发现在这个异常抛出的时间点有出现 clickhouse-server 的重启,通过监控系统看到机器的内存使用在该时间点出现高峰,在初期集群"裸奔"的时期,很多内存参数都没有进行限制,导致 clickhouse-server 内存使用量太高被 OS KILL 掉。

case study

上面推荐的内存参数强烈推荐全部加上,max_memory_usage_for_all_queries 该参数没有正确设置是导致该 case 触发的主要原因。

3)Memory limit (for query) exceeded:would use 9.37 GiB (attempt to allocate chunk of 301989888 bytes), maximum: 9.31 GiB

该异常很直接,就是我们限制了 SQL 的查询内存(max_memory_usage)使用的上线,当内存使用量大于该值的时候,查询被强制 KILL。

对于常规的如下简单的 SQL, 查询的空间复杂度为 O(1) 。

select count(1) from table where condition1 and condition2select c1, c2 from table where condition1 and condition2

对于 group by, order by , count distinct,join 这样的复杂的 SQL,查询的空间复杂度就不是 O(1)了,需要使用大量的内存。

4)zookeeper 的 snapshot 文件太大,follower 从 leader 同步文件时超时

上面有说过 clickhouse 对 zookeeper 的依赖非常的重,表的元数据信息,每个数据块的信息,每次插入的时候,数据同步的时候,都需要和 zookeeper 进行交互,上面存储的数据非常的多。

就拿我们自己的集群举例,我们集群有 60 台机器 30 张左右的表,数据一般只存储 2 天,我们 zookeeper 集群的压力 已经非常的大了,zookeeper 的节点数据已经到达 500w 左右,一个 snapshot 文件已经有 2G+左右的大小了,zookeeper 节点之间的数据同步已经经常性的出现超时。

问题解决

5)zookeeper 压力太大,clickhouse 表处于”read only mode”,插入失败

 

 

原文链接:https://www.infoq.cn/article/g94gmf26m6ontneoe0r5

标签:数据,快及,zookeeper,查询,引擎,内存,优化,clickhouse
来源: https://www.cnblogs.com/robots2/p/16520851.html