原文链接:袋鼠云数栈基于CBO在Spark SQL优化上的摸索
一、Spark SQL CBO选型背景
Spark SQL的优化器有两种优化形式:一种是基于规定的优化形式(Rule-Based Optimizer,简称为RBO);另一种是基于代价的优化形式(Cost-Based Optimizer,简称为CBO)。
1、RBO是传统的SQL优化技术
RBO是倒退比拟早且比拟成熟的一项SQL优化技术,它依照制订好的一系列优化规定对SQL语法表达式进行转换,最终生成一个最优的执行打算。RBO属于一种教训式的优化办法,严格依照既定的规定程序进行匹配,所以不同的SQL写法间接决定执行效率不同。且RBO对数据不敏感,在表大小固定的状况下,无论两头后果数据怎么变动,只有SQL放弃不变,生成的执行打算就都是固定的。
2、CBO是RBO改良演变的优化形式
CBO是对RBO改良演变的优化形式,它能依据优化规定对关系表达式进行转换,生成多个执行打算,在依据统计信息(Statistics)和代价模型(Cost Model)计算得出代价最小的物理执行打算。
3、 CBO与RBO劣势比照
● RBO优化例子
上面咱们来看一个例子:计算t1表(大小为:2G)和t2表(大小为:1.8G)join后的行数
上图是:
SELECT COUNT(t1.id) FROM t1 JOIN t2 ON t1.id = t2.id WHERE t1.age > 24
基于RBO优化后生成的物理执行打算图。在图中咱们能够看出,执行打算最初是选用SortMergeJoin ⑴ 进行两个表join的。
在Spark中,join的实现有三种:
1.Broadcast Join
2.ShuffleHash Join
3.SortMerge Join
ShuffleHash Join和SortMerge Join都须要shuffle,绝对Broadcast Join来说代价要大很多,如果选用Broadcast Join则须要满足有一张表的大小是小于等于
spark.sql.autoBroadcastJoinThreshold 的大小(默认为10M)。
而咱们再看,上图的执行打算t1表,原表大小2G过滤后10M,t2表原表大小1.8G过滤后1.5G。这阐明RBO优化器不关怀两头数据的变动,仅依据原表大小进行join的抉择了SortMergeJoin作为最终的join,显然这失去的执行打算不是最优的。
● CBO优化例子
而应用CBO优化器失去的执行打算图如下:
咱们不难看出,CBO优化器充分考虑到两头后果,感知到两头后果的变动满足能Broadcast Join的条件,所以生成的最终执行打算会抉择Broadcast Join来进行两个表join。
● 其余劣势
其实除了刻板的执行导致不能失去最优解的问题,RBO还有学习老本高的问题:开发人员须要相熟大部分优化规定,否则写进去的SQL性能可能会很差。
● CBO是数栈Spark SQL 优化的更佳抉择
绝对于RBO,CBO无疑是更好的抉择,它使Spark SQL的性能晋升上了一个新台阶,Spark作为数栈平台底层十分重要的组件之一,承载着离线开发平台上大部分工作,做好Spark的优化也将推动着数栈在应用上更加高效易用。所以数栈抉择CBO做钻研摸索,由此进一步提高数栈产品性能。
二、Spark SQL CBO实现原理
Spark SQL中实现CBO的步骤分为两大部分,第一局部是统计信息收集,第二局部是老本估算:
1、统计信息收集
统计信息收集分为两个局部:第一局部是原始表信息统计、第二局部是两头算子的信息统计。
1)原始表信息统计
Spark中,通过减少新的SQL语法ANALYZE TABLE来用于统计原始表信息。原始表统计信息分为表级别和列级别两大类,具体的执行如下所示:
● 表级别统计信息
通过执行 ANALYZE TABLE table_name COMPUTE STATISTICS 语句来收集,统计指标包含estimatedSize解压后数据的大小、rowCount数据总条数等。
● 列级别统计信息
通过执行 ANALYZE TABLE table_name COMPUTE STATISTICS FOR COLUMNS column-name1, column-name2, …. 语句来收集。
列级别的信息又分为根本列信息和直方图,根本列信息包含列类型、Max、Min、number of nulls, number of distinct values, max column length, average column length等,直方图形容了数据的散布。Spark默认没有开启直方图统计,须要额定设置参数:spark.sql.statistics.histogram.enabled = true。
原始表的信息统计绝对简略,推算两头节点的统计信息绝对就简单一些,并且不同的算子会有不同的推算规定,在Spark中算子有很多,有趣味的同学能够看Spark SQL CBO设计文档:
https://issues.apache.org/jir...
2)两头算子的信息统计
咱们这里以常见的filter算子为例,看看推算算子统计信息的过程。基于上一节的SQL SELECT COUNT(t1.id) FROM t1 JOIN t2 ON t1.id = t2.id WHERE t1.age > 24生成的语法树来看下t1表中蕴含大于运算符 filter节点的统计信息。
图片
在这里须要分三种状况思考:
第一种
过滤条件常数值大于max(t1.age),返回后果为0;
第二种
过滤条件常数值小于min(t1.age),则全副返回;
第三种
过滤条件常数介于min(t1.age)和max(t1.age)之间,当没有开启直方图时过滤后统计信息的公式为after_filter = (max(t1.age) - 过滤条件常数24)/(max(t1.age) – min(t1.age)) before_filter,没有开启直方图则默认工作数据分布是平均的;当开启直方图时过滤后统计信息公式为after_filter = height(>24) / height(All) before_filter。而后将该节点min(t1.age)等于过滤条件常数24。
2、老本估算
介绍完如何统计原始表的统计信息和如何计算两头算子的统计信息,有了这些信息后就能够计算每个节点的代价老本了。
在介绍如何计算节点老本之前咱们先介绍一些老本参数的含意,如下:
Hr: 从 HDFS 读取 1 个字节的老本 Hw: 从 HDFS 写1 个字节的老本NEt: 在 Spark 集群中通过网络从任何节点传输 1 个字节到 指标节点的均匀老本Tr: 数据总条数Tsz: 数据均匀大小CPUc: CPU 老本
计算节点老本会从IO和CPU两个维度思考,每个算子老本的计算规定不一样,咱们通过join算子来举例说明如何计算算子的老本:
假如join是Broadcast Join,大表散布在n个节点上,那么CPU代价和IO代价计算公式别离如下:
CPU Cost=小表构建Hash Table的老本 + 大表探测的老本 = Tr(Rsmall) CPUc + (Tr(R1) + Tr(R2) + … + Tr(Rn)) n * CPUc
IO Cost =读取小表的老本 + 小表播送的老本 + 读取大表的老本 = Tr(Rsmall) Tsz(Rsmall) Hr + n Tr(Rsmall) Tsz(Rsmall) NEt + (Tr(R1) Tsz(R1) + … + Tr(Rn) Tsz(Rn)) Hr
然而无论哪种算子,成本计算都和参加的数据总条数、数据均匀大小等因素间接相干,这也是为什么在这之前要先介绍如何统计原表信息和推算两头算子的统计信息。
每个算子依据定义的规定计算出老本,每个算子老本相加便是整个执行打算的总成本,在这里咱们能够思考一个问题,最优执行打算是列举每个执行打算一个个算出每个的总成本得进去的吗?显然不是的,如果每个执行打算都计算一次总代价,那预计黄花菜都要凉了,Spark奇妙的应用了动静布局的思维,疾速得出了最优的执行打算。
三、数栈在Spark SQL CBO上的摸索
理解完Spark SQL CBO的实现原理之后,咱们来思考一下第一个问题:大数据平台想要实现反对Spark SQL CBO优化的话,须要做些什么?
在前文实现原理中咱们提到,Spark SQL CBO的实现分为两步,第一步是统计信息收集,第二步是老本估算。而统计信息收集又分为两步:第一步的原始表信息统计、第二步两头算子的信息统计。到这里咱们找到了第一个问题的答案:平台中须要先有原始表信息统计的性能。
第一个问题解决后,咱们须要思考第二个问题:什么时候进行表信息统计比拟适合?针对这个问题,咱们初步构想了三种解决信息统计的计划:
● 在每次SQL查问前,先进行一次表信息统计
这种形式失去的统计信息比拟精确,通过CBO优化后得出的执行打算也是最优的,然而信息统计的代价最大。
● 定期刷新表统计信息
每次SQL查问前不须要进行表信息统计,因为业务数据更新的不确定性,所以这种形式进行SQL查问时失去的表统计信息可能不是最新的,那么CBO优化后失去的执行打算有可能不是最优的。
● 在变更数据的业务方执行信息统计
这种形式对于信息统计的代价是最小的,也能保障CBO优化失去的执行打算是最优的,然而对于业务代码的侵入性是最大的。
不难看出三种计划各有利弊,所以进行表信息统计的具体计划取决于平台自身的架构设计。
基于数栈平台建设数仓的结构图如下图所示:
首先通过ChunJun将业务数据库数据采集到Hive ODS层而后通过Hive或者Spark进行数据处理最初通过ChunJun将Hive库的数据写入到业务数据库用于业务解决
从结构图可看出数栈有用到Hive、Spark和ChunJun三个组件,并且这三个组件都会读写Hive, 数栈多个子产品(如离线平台和实时平台)也都有可能对Hive进行读写,所以如果基于计划3来做老本是十分高的。
计划1自身代价就曾经较大,每次查问前都进行一次信息统计,信息统计的工夫是要算在本次查问耗时中的,如果表数据量比拟大减少的工夫可能是十几分钟甚至更久。
综合思考,咱们选用了更灵便正当的计划2来进行表信息统计。尽管Spark SQL运行时失去的统计信息可能不是最新的,然而总体相比拟RBO来说还是有很大的性能晋升。
接下来就为大家分享,数栈是如何如何统计收集原表信息统计:
咱们在离线平台项目管理页面上增加了表信息统计性能,保障了每个我的项目能够依据我的项目自身状况配置不同的触发策略。触发策略可配置按天或者按小时触发,按天触发反对配置到从当天的某一时刻触发,从而避开业务高峰期。配置结束后,到了触发的时刻离线平台就会主动以我的项目为单位提交一个Spark工作来统计项目表信息。
在数栈没有实现CBO反对之前,Spark SQL的优化只能通过调整Spark自身的参数实现。这种调优形式很高的准入门槛,须要使用者比拟相熟Spark的原理。数栈CBO的引入大大降低了使用者的学习门槛,用户只须要在Spark Conf中开启
CBO-spark.sql.cbo.enabled=true
而后在对应我的项目中配置好表信息统计就能够做到SQL优化了。
四、将来瞻望
在CBO优化方面继续投入钻研后,Spark SQL CBO整体相比拟RBO而言曾经有了很大的性能晋升。但这并不阐明整个操作系统就没有优化的空间了,曾经拿到的提高只会鼓励咱们持续进行更深层次的摸索,致力往前再迈一步。
实现对CBO的初步反对摸索后,数栈把眼光看向了Spark 3.0 版本引入的新个性——AQE(Adaptive Query Execution)。
AQE是动静CBO的优化形式,是在CBO根底上对SQL优化技术又一次的性能晋升。如前文所说,CBO目前的计算对前置的原始表信息统计是仍有依赖的,而且信息统计过期的状况会给CBO带来不小的影响。
如果在运行时动静的优化 SQL 执行打算,就不再须要像CBO那样须要提前做表信息统计。数栈正在针对这一个新个性进行,置信不久的未来咱们就能引入AQE,让数栈在易用性高性能方面更上一层楼。心愿小伙伴们放弃关注,数栈愿和大家一起成长。
原文起源:VX公众号“数栈研习社”
袋鼠云开源框架钉钉技术交换群(30537511),欢送对大数据开源我的项目有趣味的同学退出交换最新技术信息,开源我的项目库地址:https://github.com/DTStack