乐趣区

关于大数据:袋鼠云数栈基于CBO在Spark-SQL优化上的探索

原文链接:袋鼠云数栈基于 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

退出移动版