引言
ClickHouse内核分析系列文章,本文将为大家深度解读ClickHouse当前的MPP计算模型、用户资源隔离、查询限流机制,在此基础上为大家介绍阿里巴巴云数据库ClickHouse在八月份即将推出的自研弹性资源队列功能。ClickHouse开源版本当前还没有资源队列相关的规划,自研弹性资源队列的初衷是更好地解决隔离和资源利用率的问题。下文将从ClickHouse的MPP计算模型、现有的资源隔离方案展开来看ClickHouse当前在资源隔离上的痛点,最后为大家介绍我们的自研弹性资源队列功能。
MPP计算模型
在深入到资源隔离之前,这里有必要简单介绍一下ClickHouse社区纯自研的MPP计算模型,因为ClickHouse的MPP计算模型和成熟的开源MPP计算引擎(例如:Presto、HAWQ、Impala)存在着较大的差异(que xian),这使得ClickHouse的资源隔离也有一些独特的要求,同时希望这部分内容能指导用户更好地对ClickHouse查询进行调优。
ClickHouse的MPP计算模型最大的特点是:它压根没有分布式执行计划,只能通过递归子查询和广播表来解决多表关联查询,这给分布式多表关联查询带来的问题是数据shuffle爆炸。另外ClickHouse的执行计划生成过程中,仅有一些简单的filter push down,column prune规则,完全没有join reorder能力。对用户来说就是"所写即所得"的模式,要求人人都是DBA,下面将结合简单的查询例子来介绍一下ClickHouse计算模型最大的几个原则。
递归子查询
在阅读源码的过程中,我可以感受到ClickHouse前期是一个完全受母公司Yandex搜索分析业务驱动成长起来的数据库。而搜索业务场景下的Metric分析(uv / pv ...),对分布式多表关联分析的并没有很高的需求,绝大部分业务场景都可以通过简单的数据分表分析然后聚合结果(数据建模比较简单),所以从一开始ClickHouse就注定不擅长处理复杂的分布式多表关联查询,ClickHouse的内核只是把单机(单表)分析做到了性能极致。但是任何一个业务场景下都不能完全避免分布式关联分析的需求,ClickHouse采用了一套简单的Rule来处理多表关联分析的查询。
对ClickHouse有所了解的同学应该知道ClickHouse采用的是简单的节点对等架构,同时不提供任何分布式的语义保证,ClickHouse的节点中存在着两种类型的表:本地表(真实存放数据的表引擎),分布式表(代理了多个节点上的本地表,相当于"分库分表"的Proxy)。当ClickHouse的节点收到两表的Join关联分析时,问题比较收敛,无非是以下几种情况:本地表 Join 分布式表 、本地表 Join 本地表、 分布式表 Join 分布式表、分布式表 Join 本地表,这四种情况会如何执行这里先放一下,等下一小节再介绍。
接下来问题复杂化,如何解决多个Join的关联查询?ClickHouse采用递归子查询来解决这个问题,如下面的简单例子所示ClickHouse会自动把多个Join的关联查询改写成子查询进行嵌套, 规则非常简单:1)Join的左右表必须是本地表、分布式表或者子查询;2)倾向把Join的左侧变成子查询;3)从最后一个Join开始递归改写成子查询;4)对Join order不做任何改动;5)可以自动根据where条件改写Cross Join到Inner Join。下面是两个具体的例子帮助大家理解:
例1
select * from local_tabA
join (select * from dist_tabB join local_tabC on dist_tabB.key2 = local_tabC.key2) as sub_Q1
on local_tabA.key1 = sub_Q1.key1
join dist_tabD on local_tabA.key1 = dist_tabD.key1;
=============>
select * from
(select * from local_tabA join
(select * from dist_tabB join local_tabC on dist_tabB.key2 = local_tabC.key2) as sub_Q1
on local_tabA.key1 = sub_Q1.key1) as sub_Q2
join dist_tabD on sub_Q2.key1 = dist_tabD.key1;
例2
select * from local_tabA
join (select * from dist_tabB join local_tabC on dist_tabB.key2 = local_tabC.key2) as sub_Q1
on local_tabA.key1 = sub_Q1.key1
join dist_tabD on local_tabA.key1 = dist_tabD.key1;
=============>
select * from
(select * from local_tabA join
(select * from dist_tabB join local_tabC on dist_tabB.key2 = local_tabC.key2) as sub_Q1
on local_tabA.key1 = sub_Q1.key1) as sub_Q2
join dist_tabD on sub_Q2.key1 = dist_tabD.key1;
Join关联中的子查询在计算引擎里就相关于是一个本地的"临时表",只不过这个临时表的Input Stream对