SpaRk 3.0 引入了许多令人期待的功能,其中之一就是动态分区裁剪。本文将通过图文并茂的方式,帮助大家了解动态分区裁剪的概念。
静态分区裁剪
在深入探讨动态分区裁剪之前,首先需要了解 SpaRk 中的静态分区裁剪。在数据库的标准术语中,裁剪指的是优化器避免读取那些不包含我们所需数据的文件。例如,我们有如下查询 SQL:
在这个简单的查询中,我们旨在匹配 Students 表中 subject = English 的记录。显然,最愚蠢的做法是先扫描所有数据,然后再使用 subject = English 进行过滤,如下图所示:
一个更优的实现是,查询优化器将过滤条件下推至数据源,以避免全量扫描,SpaRk 正是这样处理的,如下图所示:
在静态分区裁剪中,我们的表首先是分区的,分区过滤下推的思想与前面提到的过滤条件下推一致。在这种情况下,如果查询包含针对分区列的过滤条件,则可以在实际查询中跳过许多不必要的分区,从而显著减少数据扫描和磁盘 I/O,提高计算性能。
然而,现实中的查询语句往往复杂得多。通常,我们需要在多张维表与大的事实表之间进行连接。在这种情况下,静态分区裁剪就无法再应用,因为过滤条件存在于连接表的一侧,而对裁剪有效的表在另一侧。例如,考虑以下查询语句:
对于上述查询,较差的查询引擎生成的执行计划如下:
它将两张表的数据关联后再进行过滤。当数据量较大时,效率可想而知。某些优秀的计算引擎可以进行优化,先在一张表中过滤无用数据,再执行连接,这样的效率自然优于前者。然而,如果我们手动操作,可以将 subject = English 的过滤条件下推到 Students 表中,这正是 SpaRk 3.0 所带来的动态分区裁剪优化。
动态分区裁剪
在 SpaRk SQL 中,用户通常使用他们熟悉的编程语言和 API 提交查询,这也是 DataFrames 和 DataSet 的由来。SpaRk 将查询转换为一种易于理解的形式,即查询的逻辑计划。在这一阶段,SpaRk 通过一系列基于规则的转换来优化逻辑计划,随后进入实际的物理计划阶段。在物理规划阶段,SpaRk 生成一个可执行的计划,将计算分配至集群。我将在逻辑计划阶段解释如何实现动态分区裁剪,然后探讨如何在物理计划阶段进一步优化。
逻辑计划阶段优化
假设我们有一个多分区的事实表,为了方便说明,使用不同颜色表示不同的分区。同时,我们有一个较小的维度表,该维度表并不是分区表。现在在这些数据集上进行典型的扫描操作。假设我们仅从维度表读取两行数据,而这两行数据对应着事实表的两个分区。因此,在执行连接操作时,带分区的事实表只需读取这两个分区的数据。
因此,我们无需实际扫描整个事实表。为实现这一优化,可以通过维度表构造一个过滤子查询,并在扫描事实表之前加上该子查询。
通过这种方式,我们在逻辑计划阶段就能确定事实表需要扫描哪些分区。
然而,上述物理计划执行效率仍然较低,因为存在重复的子查询。我们需要找到一种方法来消除这些重复的子查询。为此,SpaRk 在物理计划阶段进行了优化。
物理计划阶段优化
如果维度表较小,SpaRk 可能会以广播哈希连接(broadcast hash join)的形式执行连接。广播哈希连接的实现是将小表的数据广播到所有的 SpaRk Executor 端,这个广播过程与我们手动广播数据的方式相似:首先通过 collect 算子将小表数据从 Executor 端拉到 Driver 端,然后在 Driver 端调用 broadcast 广播到所有 Executor 端。与此同时,大表也会构建哈希表(称为 build Relation),在 Executor 端,广播的数据将与大表的相应分区进行连接,这种连接策略避免了 Shuffle 操作。具体如下:
我们已经了解了广播哈希连接的实现原理。实际上,动态分区裁剪优化就是在广播哈希连接中,当大表进行 build Relation 时,利用维度表的广播结果进行动态过滤,从而避免扫描无用数据。具体如下:
以上就是动态分区裁剪在逻辑计划和物理计划阶段的优化方法。
动态分区裁剪适用条件
并非所有查询都会启用动态裁剪优化,必须满足以下几个条件:
参数必须设置为 true,默认情况下是启用的;需要裁剪的表必须是分区表,并且分区字段必须在连接的 on 条件中;连接类型必须为 INNER、LEFT SEMI(左表是分区表)、LEFT OUTER(右表是分区表)或 RIGHT OUTER(左表是分区表)。即使满足上述条件,动态分区裁剪仍需依据两个参数综合评估其是否有益,只有在满足条件的情况下,才会进行动态分区裁剪。
[[[IMG_1]]]
[[[IMG_2]]]
[[[IMG_3]]]
[[[IMG_4]]]
[[[IMG_5]]]
[[[IMG_6]]]
