Spark是否仅在显示的记录上应用我的UDF?

时间:2017-03-28 19:20:26

标签: scala apache-spark

我有一种感觉Spark比我更聪明,并且重新排序(或者至少与编写的代码相比)执行程序上运行的内容等。

假设我在scala中有一个非常简单的spark查询,如下所示。

val sqlContext = new org.apache.spark.sql.hive.HiveContext(sc)
val rawData = sqlContext.sql("FROM mytable SELECT *")

然后我使用UDF中的某些功能创建一个新列,此功能不是轻量级的(或者至少在某些时候)并且依赖于数据中的多个列。粗略地说,我的UDF看起来与此类似,尽管处理只是一个例子。

def method1(s1:String, s2:String):String = {
  List(s1, s2).mkString(" ")
}

val method1UDF = udf(method1 _)

val dataWithCol = rawData
                  .withColumn("newcol", method1UDF($"c1",$"c2"))

dataWithCol.show(100)

我的问题实际上围绕着最后一个陈述,或者至少我认为是这样。

如果我的数据集中有10亿条记录,那么Spark实际上只将我的withColumn应用于100条记录,还是将它应用于所有100万条记录然后只返回前100条记录?

在Hive中,我认为等价物是:

SELECT t.c1, t.c2, CONCAT_WS(" ",t.c1,t.c2) as newCol from (
    SELECT c1,c2 as newCol FROM mytable limit 100
) t

即使在代码中,我看起来已经编写了以下查询的等效内容

SELECT * from (
    SELECT c1,c2, CONCAT_WS(" ",c1,c2) as newCol FROM mytable  
) t limit 100

我怀疑它正在做前者,因为在新列上添加过滤器会大大减慢操作速度。如果我将最后一行更改为:

dataWithCol.filter($"newCol" === "H i").show(100)

现在必须将该函数应用于更多数据(可能是整个数据集),然后才能达到100的限制,类似于以下Hive查询:

SELECT * from (
    SELECT c1,c2, CONCAT_WS(" ",c1,c2) as newCol FROM mytable  
) t where t.newCol == "H i" limit 100

我是否与Spark在后台正在做的事情一致?它是通过仅对最终被查看的记录应用处理来优化我的查询吗?

2 个答案:

答案 0 :(得分:1)

Spark应用了一些名为" lazy execution"的东西。这意味着它仅在必要时评估操作。所以,它实际上是在你写的两个语句之间做了一些事情。执行计划员足够聪明,可以弄清楚需要做什么,以及什么不做。要查看更多详细信息,请浏览到localhost:4040(对于您正在运行的每个上下文,将端口增加1)。

答案 1 :(得分:1)

如果您不确定您是否可以进行实验:

Spark context available as 'sc' (master = local[*], app id = local-1490732267478).
Spark session available as 'spark'.
Welcome to
      ____              __
     / __/__  ___ _____/ /__
    _\ \/ _ \/ _ `/ __/  '_/
   /___/ .__/\_,_/_/ /_/\_\   version 2.1.0
      /_/

Using Scala version 2.11.8 (OpenJDK 64-Bit Server VM, Java 1.8.0_121)
Type in expressions to have them evaluated.
Type :help for more information.

scala> :paste
// Entering paste mode (ctrl-D to finish)

val rawData = spark.range(0, 1000000000, 1, 1000)
  .toDF("id")
  .select(
    $"id".cast("string").alias("s1"), 
    $"id".cast("string").alias("s2"))

val counter = sc.longAccumulator("counter")

def f = udf((s1: String, s2: String) => {
  counter.add(1)
  s"$s1 $s2"
})

rawData.select(f($"s1", $"s2")).show(10)



// Exiting paste mode, now interpreting.
+-----------+
|UDF(s1, s2)|
+-----------+
|        0 0|
|        1 1|
|        2 2|
|        3 3|
|        4 4|
|        5 5|
|        6 6|
|        7 7|
|        8 8|
|        9 9|
+-----------+
only showing top 10 rows

rawData: org.apache.spark.sql.DataFrame = [s1: string, s2: string]
counter: org.apache.spark.util.LongAccumulator = LongAccumulator(id: 0, name: Some(counter), value: 12)
f: org.apache.spark.sql.expressions.UserDefinedFunction

scala> counter.value
res1: Long = 12

正如您所看到的,Spark限制了要处理的记录数,但并不完全准确。您还应该记住,这些结果取决于版本和查询。

例如早期的Spark版本,在将优化应用于UDF调用时相当有限。上游广泛转换也可能会影响此行为,并导致处理更多(甚至所有)记录。