ClickHouse 数据库中的 “超时”
一、超时类型
关于Clickhouse数据库超时问题,需要注意三种不同的超时设置:
1.1 distributed_ddl_task_timeout超时问题:
- 这是分布式DDL查询(带有 on cluster )的执行等待时间,默认值为180秒。
- 可以通过在DMS上执行命令来设置全局参数:set global on cluster ck_cluster distributed_ddl_task_timeout = 1800;
- 分布式DDL基于clickhouse-keeper构建任务队列异步执行,执行等待超时并不代表查询失败,只是表示之前发送的任务还在排队等待执行。可以在system.distrubuted_ddl_queue表中查到。
- 这个超时的出现,一般是有什么异常DDL操作卡住了,导致后面的所有DDL语句都是一种超时状态。解决这个卡住的DDL语句就可以啦。
1.2 max_execution_time超时问题:
- 这是一般查询的执行超时时间,默认值为3600秒。
- 用户可以进行查询级别更改,例如:select * from system.numbers settings max_execution_time = 3600;
- 可以在DMS上执行以下命令来设置全局参数:set global on cluster default max_execution_time = 3600;
- 该参数在chproxy 代理上也可以设置(用到9090端口)
1.3 socket_timeout超时问题:
- 这是HTTP协议在监听socket返回结果时的等待时间
- 该参数不是Clickhouse系统内的参数,而是属于jdbc在HTTP协议上的参数。它会影响到前面的max_execution_time参数设置的效果,因为它决定了客户端在等待结果返回时间上的时间限制
- 用户在调整 max_execution_time 参数的时候也需要配套调整 socket_timeout 参数,例如:jdbc:clickhouse://127.0.0.1:9090/ceelake?socket_timeout=3600000
所以这也是为什么许多应用超过30s就报超时的原因
二、insert数据超时优化策略
⚠️优化策略需要在确保插入语句没有问题之后才需要考虑,这里需要提前注意下不是因为 select 超时而导致insert 超时
2.1 max_insert_threads
默认值:0
增加并发插入线程----对于大数据量的插入操作,尤其是宽表(列非常多),默认的自动设置可能无法最大化利用系统的资源,导致插入速度较慢。
如果你的机器有多个CPU核心,可以考虑手动增加max_insert_threads的值,以提高插入的并发度。你可以将其设置为与CPU核心数相同,或者略高一些。例如:
SETTINGS max_insert_threads = 16; -- 根据你服务器的核心数调整
这可以增加插入操作的并行度,尤其是在大数据量的插入时,这样每个线程可以处理不同的数据分块,提高整体插入吞吐量。
实例:700多列 3000万数据的insert (大宽表)
2.2 insert_quorum
如果你的ClickHouse集群是分布式的,可以调整insert_quorum来控制写入的节点数量。减少insert_quorum可以减少等待节点确认的时间,从而提升插入速度。比如:
SETTINGS insert_quorum = 2; -- 根据集群规模调整
这个设置会使得插入操作在部分节点完成后就返回成功,从而加速插入过程。
2.3 max_memory_usage
插入大数据量时,内存消耗也很高。你可以通过调整max_memory_usage来限制每个插入操作的内存使用,避免因内存不足导致的瓶颈:
SETTINGS max_memory_usage = 10000000000; -- 设置为合适的内存限制
2.4 批量插入
如果一次性插入的数据量过大,尝试将数据分批插入。ClickHouse在处理大批量数据时,分批插入可以减少资源压力。例如,每次插入100万条数据,而不是一次插入500万条数据。
2.5 适当调整max_insert_block_size
默认值:1048449
可以调整max_insert_block_size来限制每个插入块的大小。虽然默认设置一般适合,但在某些情况下,较小的块大小可能会提高插入性能:
SETTINGS max_insert_block_size = 1048576; -- 例如:1MB大小
插入表时要形成的块的大小(以行数表示)。此设置仅适用于服务器形成块的情况。例如,对于通过HTTP接口的INSERT,服务器解析数据格式并形成指定大小的块。但是,当使用clickhouse客户端时,客户端会自己解析数据,服务器上的“max_insert_block_size”设置不会影响插入块的大小。使用INSERT SELECT时,该设置也没有目的,因为数据是使用SELECT后形成的相同块插入的。
默认值略大于max_block_size。这样做的原因是,某些表引擎(*MergeTree)在磁盘上为每个插入的块形成一个数据部分,这是一个相当大的实体。同样,*MergeTree表在插入过程中对数据进行排序,足够大的块大小允许在RAM中对更多数据进行排序。
2.6 并行批量插入
可以利用并行化工具(如clickhouse-client的并行插入功能,或者通过编写多线程的脚本)来进一步提高插入性能。通过并行插入多个小批次,可以提高吞吐量。
三、select 数据超时优化策略
3.0 语句优化
除了数据本身的问题,例如是否有字段大量使用NLLAble(影响性能), 具体查询语句具体优化;
3.1 单节点join
clickhouse单机join默认采用的是hash join算法,也就是说,先将右表全量读取到内存,然后构建hashmap,然后从左表分批读取数据,到hashmap中去匹配,如果命中,那么就作为join之后的结果输出。
因为右表是要全量加载到内存的,这就要求要足够小。但是实际上右表大于TB级是很常见的事情,这时候就很容易出现OOM。
为了解决这个问题,有一种解决思路是将右表构建成大宽表,将维度拍平,使行尽量少,这样右表只需要加载少量的列进内存,从而缓解这一情况。
一个典型的落地实施案例就是clickhouse_sinker存储Prometheus指标的方案。它将指标分拆成两张表,一张metric表,用来存储时序指标值,一张metric_series表,用来存储具体的指标。两张表通过series_id进行关联。
在metric表中,每个时间点的每个值,都对应着一个series_id,我们通过这个series_id,反查metric_series表,就可以找到这个series_id对应的指标以及label。
3.2 分布式join
clickhouse的分布式join有两种玩法,一种是带global,一种是不带global。
普通join
汇总节点将左表替换为本地表
将左表的本地表分发到每个节点
在每个节点执行本地join
将结果汇总回汇总节点
这种做法有一个非常严重的问题。在第三步,每个节点执行分布式join的时候,如果右表也是分布式表,那么集群中的每个节点都要去执行分布式查询,那也就是说,如果集群有N个节点,右表查询就会在集群中执行N*N次,这就是查询放大现象。(对这个感兴趣的可以看看文档。。。。 ,具体讲述了查询放大的现象)
正是因为这种问题的存在,在比较新的clickhouse版本中,分布式join如果不加global,已经会从语法层面报错了。这说明官方已经禁止了这种写法的存在。
global join
汇总节点将右表改成子查询,先在汇总节点将右表的数据结果集查询出来
将右表的结果集广播给各个节点,与各个节点的左表本地表进行join查询
各个节点将查询结果发送给汇总节点
由于右表的结果已经在汇总节点计算出来了,那么也就不需要在其他节点重复计算,从而避免了读放大的问题。
但global join 的问题是它需要将整个子查询的结果集发送给各个节点,如果右表的结果集特别大,那么整个过程耗费的网络带宽也将是非常恐怖的。
正确姿势
- 大表在左,小表在右
- 数据预分布,实现colocate join。也就是将涉及到join的表按照相同的join key分片,使需要join的数据尽量都能在本地完成。也就是前文提到的shardingkey的用处。
- 改大宽表
并发查询
并发查询也是clickhouse的一个比较薄弱的领域。
因为对于clickhouse的查询来说,一个大的查询SQL往往会把整个服务器的资源吃满,如果并发查询比较多的话,那么不可避免地造成资源竞争,最终的结果就是谁也快不了,甚至还会出现OOM的情况。
官方默认的最大并发数是100, 这个100是每个节点的最大并发数,而不是整个集群的。它包含了查询的并发和写入的并发。
我们很容易碰到这样的场景:在界面上点击一个查询耗时比较久,等得不耐烦,就多点了几下。事实上,clickhouse并不会因为你在界面上多点了一下鼠标,就取消之前的SQL运行,反而会产生多个SQL在并发执行,如果这种耗时比较久的SQL越积CPU打满,造成的结果就是恶性循环,其他SQL也会越来越慢,最终导致并发的SQL超过了设置的最大并发数,再也无法执行任何查询语句,甚至写入都会受到影响。
我们一般建议将最大查询并发数,设置为最大并发数的90%,预留出10%的并发数量供数据写入使用。这样即使查询并发数打满了,仍然不会影响到数据的写入。
我们可以通过配置来实现这一策略:
<clickhouse><max_concurrent_queries>100</max_concurrent_queries><max_concurrent_select_queries>90</max_concurrent_select_queries>
</clickhouse>