Spark-SQL中的连接性能

时间:2016-09-21 11:23:21

标签: apache-spark apache-spark-sql

假设我们有一个健康的集群,并且对于用例我们有

  

两个数据集,其中包含1个Billlion +记录

我们需要比较两个数据集并找出

  

原始数据集中的重复项

我打算写一个

  

对要检查的列进行连接的SQL查询   重复

我想知道

将是怎样的
  

此查询的效果以及优化

可以在加入数据集之前在数据集(数据框分区)中完成。

请参与您的观察。

3 个答案:

答案 0 :(得分:3)

  

我想知道性能如何

相比什么?至于绝对数字,我认为这显然取决于你的数据和你的集群。

然而,在Spark 2.0中,性能的提升非常显着。

  

和改进

Catalyst优化器相当不错(在2.0之后更是如此)。在它下面负责大部分优化,如列修剪,谓词下推等。(在2.0中,还有代码生成,负责生成一个非常优化的代码,实现非常大的性能改进。)

无论您使用DataFrames / Datasets API还是SQL,这些相同的改进都是可用的。

作为Spark的催化剂执行的一种查询优化的例子,假设你有两个数据帧df1和df2具有相同的模式(根据你的情况),你想在一些列上加入它们只得到交集并输出那些元组。

假设我的数据帧架构如下(调用df.schema):

StructType(
StructField(df.id,StringType,true), 
StructField(df.age,StringType,true), 
StructField(df.city,StringType,true), 
StructField(df.name,StringType,true))

我们的数据集中包含id,age,city,name列。

现在给出你想做的事情,你会做类似

的事情
df1.join(
    df2, 
    $"df2.name"===$"df1.name"
       ).select("df1.id","df1.name", "df1.age", "df1.city" ).show

如果你看一下上面的物理计划,你会发现Catalyst优化器在幕后做了很多优化:

== Physical Plan ==
*Project [df1.id#898, df1.name#904, df1.age#886, df1.city#892]
+- *BroadcastHashJoin [df1.name#904], [df2.name#880], Inner, BuildRight
   :- *Project [age#96 AS df1.age#886, city#97 AS df1.city#892, id#98 AS df1.id#898, name#99 AS df1.name#904]
   :  +- *Filter isnotnull(name#99)
   :     +- *Scan json [age#96,city#97,id#98,name#99] Format: JSON, PushedFilters: [IsNotNull(name)], ReadSchema: struct<age:string,city:string,id:string,name:string>
   +- BroadcastExchange HashedRelationBroadcastMode(List(input[0, string, true]))
      +- *Project [name#99 AS df2.name#880]
         +- *Filter isnotnull(name#99)
            +- *Scan json [name#99] Format: JSON, PushedFilters: [IsNotNull(name)], ReadSchema: struct<name:string>

`

特别要注意的是,即使连接两个相同的数据帧,它们的读取方式也不同 -

  1. Predicate下推:从查询中可以明显看出,对于df2,您需要的只是name列(而不是id, age等的整个记录​​)。如果将这些信息推送到我的数据被读取的位置,这不是很好吗?这将使我免于阅读我不打算使用的不必要的数据。这正是Spark所做的!对于加入的一方,Spark只会读取name列。这一行: +- *Scan json [name#99] Format: JSON, PushedFilters: [IsNotNull(name)], ReadSchema: struct<name:string>但是对于另一方df1,我们希望在加入后结果中的所有四列。 Spark再次说明了这一点,并且在那一侧它会读取所有四列。这一行:+- *Scan json [age#96,city#97,id#98,name#99] Format: JSON, PushedFilters: [IsNotNull(name)], ReadSchema: struct<age:string,city:string,id:string,name:string>

  2. 此外,在阅读之后,加入Spark之前发现您正在加入name列。因此,在加入之前,它删除了名为null的元组。这一行:+- *Filter isnotnull(name#99)

  3. 这意味着Spark已经为您完成了所有这些繁重的工作,以便读取最小数据并将其带入内存(从而减少随机播放和计算时间)。

    但是,对于您的具体情况,您可能想要考虑是否可以进一步减少此数据的读取 - 至少对于连接的一侧。如果df2中有许多行具有与df1匹配的相同键组合,该怎么办?首先在df2上做一个截然不同的你会不会更好?即类似的东西:

    df1.join(
        df2.select("df2.name").distinct, 
        $"df2.name"===$"df1.name"
       ).select("df1.id","df1.name", "df1.age", "df1.city" )
    

答案 1 :(得分:2)

此类订单的数据集的查询性能无法预测,但可以进行处理。我使用了7亿条记录的数据集,以下是帮助调整我的应用程序的重点属性。

  • spark.sql.shuffle.partitions(自己找到最佳位置)
  • spark.serializer(最好是KryoSerializer)

同样为应用程序分配群集资源非常重要。请参阅此blog。感谢。

答案 2 :(得分:0)

您是否尝试过根据您的群集配置将执行程序核心增加到4个或更多,并且在执行spark-submit时更好,更不用说不。执行人让火花决定不。要使用的执行者。在处理大型数据集时,这可能会在一定程度上提高性能。