Spark SQL是否对过滤的equi-joins进行谓词下推?

时间:2016-03-27 12:24:38

标签: python apache-spark dataframe pyspark apache-spark-sql

我有兴趣使用Spark SQL(1.6)来执行"过滤的equi-joins"形式

A inner join B where A.group_id = B.group_id and pair_filter_udf(A[cols], B[cols])

此处group_id是粗略的:单个group_id值可以与A和B中的10,000条记录相关联。

如果equi-join是自己执行的,没有pair_filter_udfgroup_id的粗糙会产生计算问题。例如,对于A和B中包含10,000条记录的group_id,连接中将有1亿条目。如果我们有成千上万的这样大的组,我们会生成一个巨大的表,我们很容易耗尽内存。

因此,我们必须将pair_filter_udf下推到连接中,并在生成它们时对其进行过滤,而不是等到生成所有对。我的问题是Spark SQL是否会这样做。

我设置了一个简单的过滤的equi-join,并询问Spark的查询计划是什么:

# run in PySpark Shell
import pyspark.sql.functions as F

sq = sqlContext
n=100
g=10
a = sq.range(n)
a = a.withColumn('grp',F.floor(a['id']/g)*g)
a = a.withColumnRenamed('id','id_a')

b = sq.range(n)
b = b.withColumn('grp',F.floor(b['id']/g)*g)
b = b.withColumnRenamed('id','id_b')

c = a.join(b,(a.grp == b.grp) & (F.abs(a['id_a'] - b['id_b']) < 2)).drop(b['grp'])
c = c.sort('id_a')
c = c[['grp','id_a','id_b']]
c.explain()

结果:

== Physical Plan ==
Sort [id_a#21L ASC], true, 0
+- ConvertToUnsafe
   +- Exchange rangepartitioning(id_a#21L ASC,200), None
      +- ConvertToSafe
         +- Project [grp#20L,id_a#21L,id_b#24L]
            +- Filter (abs((id_a#21L - id_b#24L)) < 2)
               +- SortMergeJoin [grp#20L], [grp#23L]
                  :- Sort [grp#20L ASC], false, 0
                  :  +- TungstenExchange hashpartitioning(grp#20L,200), None
                  :     +- Project [id#19L AS id_a#21L,(FLOOR((cast(id#19L as double) / 10.0)) * 10) AS grp#20L]
                  :        +- Scan ExistingRDD[id#19L] 
                  +- Sort [grp#23L ASC], false, 0
                     +- TungstenExchange hashpartitioning(grp#23L,200), None
                        +- Project [id#22L AS id_b#24L,(FLOOR((cast(id#22L as double) / 10.0)) * 10) AS grp#23L]
                           +- Scan ExistingRDD[id#22L]

这些是该计划的关键路线:

+- Filter (abs((id_a#21L - id_b#24L)) < 2)
    +- SortMergeJoin [grp#20L], [grp#23L]

这些行给人的印象是过滤器将在连接后的单独阶段完成,这不是所需的行为。但也许它被隐式地推入了联接,而查询计划只是缺乏那种细节。

在这种情况下,我怎么知道Spark正在做什么?

更新:

我正在运行n = 1e6和g = 1e5的实验,如果Spark没有进行下推,这应该足以让我的笔记本电脑崩溃。由于它没有崩溃,我猜它正在做下推。但是知道它是如何工作以及Spark SQL源的哪些部分负责这个令人敬畏的优化会很有趣。

1 个答案:

答案 0 :(得分:5)

很大程度上取决于下推的含义。如果您询问|a.id_a - b.id_b| < 2是否作为join旁边a.grp = b.grp逻辑的一部分执行,则答案为否定。不基于相等性的谓词不直接包含在join条件中。

您可以说明使用DAG而不是执行计划的一种方式它应该看起来或多或少像这样:

enter image description here

如您所见,filter作为与SortMergeJoin的单独转换执行。另一种方法是在删除a.grp = b.grp时分析执行计划。您会看到它将join展开为笛卡尔积,然后展开filter而不进行其他优化:

d = a.join(b,(F.abs(a['id_a'] - b['id_b']) < 2)).drop(b['grp'])

## == Physical Plan ==
## Project [id_a#2L,grp#1L,id_b#5L]
## +- Filter (abs((id_a#2L - id_b#5L)) < 2)
##    +- CartesianProduct
##       :- ConvertToSafe
##       :  +- Project [id#0L AS id_a#2L,(FLOOR((cast(id#0L as double) / 10.0)) * 10) AS grp#1L]
##       :     +- Scan ExistingRDD[id#0L] 
##       +- ConvertToSafe
##          +- Project [id#3L AS id_b#5L]
##             +- Scan ExistingRDD[id#3L]

这是否意味着你的代码(不是笛卡尔的代码 - 你真的想在实践中避免这种代码)会生成一个巨大的中间表吗?

不,它没有。 SortMergeJoinfilter都作为单个阶段执行(请参阅DAG)。虽然DataFrame操作的某些细节可以应用在略低的级别,但它基本上只是Scala Iteratorsas shown in a very illustrative way by Justin Pihony上的转换链,可以将不同的操作压缩在一起不添加任何特定于Spark的逻辑。这两种过滤器都可以在一个任务中应用。