内容来源于《Spak快速大数据分析》
目录
一、动态分区裁剪
动态分区裁剪(dynamic partition pruning,DPP)背后的思路是跳过计算查询结果所不需要的数据。动态分区裁剪效果最好的典型场景是连接两张表,其中一张表为事实表(多列数据的分区表),另一张表为维度表(未分区),如图 1 所示。通常情况下,过滤条件来自未分区的那张表(在本例中是 Date 这张表)。举个例子,思考对 Sales 和 Date 这两张表使用这样一条常见查询。
Select * from Sales join 'Date' ON Sales.date = Date.date |
图1
动态分区裁剪优化技术的关键是,将维度表过滤结果注入扫描事实表的操作,使其成为扫描时的过滤条件,从而限制读取的数据量.
假设执行查询时的维度表比事实表小很多,如图 2 所示。在这种情况下,Spark 很可能会执行广播连接。在连接过程中,Spark 会执行下列步骤,最小化从较大的事实表扫描的数据量。
在连接的维度表那一边,对维度表(也称为构建关系)执行过滤查询时,Spark 会根据其读取结果构建哈希表。Spark 将这一侧查询的结果放入哈希表中,然后赋值给一个广播变量。该广播变量会被分发到所有执行这个连接操作的执行器。在每个执行器中,Spark 会查询这个广播的哈希表,从而判断哪些相关的行需要从事实表中读出来。
最后,Spark 会将这个过滤条件动态插入事实表的文件扫描操作中,并重用广播变量中的结果。这样一来,对事实表进行文件扫描操作时,就只会扫描能匹配过滤条件的分区,并且只读取需要的数据。
如图 2
如图 2
这一功能默认启用,无须显式配置,执行表连接时会自动生效。有了动态分区裁剪,Spark 3.0 对星型结构的查询更加游刃有余
二、自适应查询执行
Spark 3.0 优化查询性能的另一大法宝是,运行时自适应调整物理执行计划。自适应查询执行(Adaptive Query Execution,AQE)会在查询执行的过程中根据运行时收集的统计信息重新优化和调整查询计划。它会在运行时执行以下内容。
在数据混洗阶段通过减少混洗的分区数来减少归约任务的数量。优化查询的物理执行计划,比如在合适的时候将 SortMergeJoin 转为 BroadcastHashJoin。在连接表时处理数据倾斜。所有这些自适应手段都是在查询计划执行时运用的,如图 3 所示。要想在Spark 3.0 中使用自适应查询执行功能,需要将 spark.sql.adaptive.enabled 设置为 true。
如图 3
自适应查询执行框架
查询任务中的 Spark 操作是串起来在并行进程中执行的,但数据混洗或数据广播会打断流水线的执行,因为一个执行阶段的输出需要作为下一个执行阶段的输入。这些断点被称为查询阶段的物化点(materialization point)。物化点给了我们再次优化和重新审视查询的机会,如图 4 所示。
如图 4
以下是自适应查询执行框架迭代的主要步骤,可以参照图 4 理解。
针对每个执行阶段,执行其中的所有叶子节点(比如扫描操作)。
执行完成后,将物化点标记为已完成状态,逻辑计划中相关的统计信息也都由执行过程中收集到的数据更新。根据统计信息(如读取的分区数、读取的数据量等),框架会再次调用 Catalyst 优化器,看看是否能够完成以下任务。
a. 合并分区,通过减少分区数来减少读取混洗数据的归约任务数量。
b. 根据所读表的大小,在可行的情况下,将排序合并连接替换为广播连接。
c. 尝试解决连接中的数据倾斜问题。
d. 创建新的逻辑计划,然后相应地生成优化后的新物理计划。这个过程不断重复,直到查询计划的所有执行阶段都执行完成。
简单地说,这种二次优化是动态执行的,如图 3 所示,目标是动态合并混洗分区,减少读取混洗输出数据所需要的归约任务的数量,在合适的时候转换连接策略,并且解决连接时出现的数据倾斜问题。
以下两个 Spark SQL 配置项控制自适应查询执行如何减少归约任务的数量。
spark.sql.adaptive.coalescePartitions.enabled(设置为 true)
spark.sql.adaptive.skewJoin.enabled(设置为 true)