Spark:懒惰的动作?

时间:2018-06-25 10:18:11

标签: performance apache-spark join memory lazy-evaluation

我正在处理一个复杂的应用程序。根据源数据,我们可以计算许多统计数据,例如。

<video autoplay webkit-playsinline playsinline style="width: 500px">
    <source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4">
</video>

由于数据框被分组在同一列上,因此结果数据框随后被分组在一起:

.container {
  max-width: 1200px;
  margin: 0 auto;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}

.text {
  font-size: 120px;
  font-weight: 900;
}

.container .text .text-row span {
  
float: left;
   
  
}

(在我的代码中,这是循环完成的)

这不是高效能的,问题在于每个数据帧(我大约有30个)以一个动作结束,因此据我了解,每个数据帧都经过计算并返回给驱动程序,然后驱动程序将数据发回执行程序以执行联接

这给了我内存错误,我可以增加驱动程序的内存,但是我正在寻找一种更好的方法。对于前。如果所有数据帧仅在最后计算(保存连接的数据帧),我想一切都将由集群管理。

有没有办法做一种懒惰的动作?还是应该以其他方式加入数据框?

Thx

1 个答案:

答案 0 :(得分:0)

首先,您显示的代码仅包含一个类似操作的操作-DataFrameWriter.save。所有其他组件都是惰性的。

但是懒惰在这里并没有真正帮助您。最大的问题(假设没有难看的数据偏斜或配置错误的广播)是各个聚合需要单独的混洗和昂贵的后续合并。

一个幼稚的解决方案是利用:

  

数据框分组在同一列上

先洗牌

val groupColumns: Seq[Column] = ???

val sourceDataPartitioned =  sourceData.groupBy(groupColumns: _*)

并使用结果来计算单个聚合

val df1 = sourceDataPartitioned
  ...

val df2 = sourceDataPartitioned
  ...

但是,这种方法比较脆弱,并且在存在大型/偏斜群体的情况下不太可能扩展。

因此,最好重写代码以仅执行聚合。幸运的是,standard SQL behavior就是您所需要的。

让我们开始使用以下代码将代码结构化为三个元素元组:

  • _1是谓词(您在filter中使用的条件)。
  • _2是要为其计算聚合的Columns的列表。
  • _3是一个聚合函数。

示例结构如下所示:

import org.apache.spark.sql.Column
import org.apache.spark.sql.functions.{count, min}

val ops: Seq[(Column, Seq[Column], Column => Column)] = Seq(
  ($"col1" === "a" and $"col2" === "b", Seq($"col3", $"col4"), count),
  ($"col2" === "b" and $"col3" === "c", Seq($"col4", $"col5"), min)
)

现在,您可以使用

编写聚合表达式
agg_function(when(predicate, column)) 

模式

import org.apache.spark.sql.functions.when

val exprs: Seq[Column] = ops.flatMap {
    case (p, cols, f) => cols.map {
      case c => f(when(p, c))
    }
}

并在sourceData

上使用它
sourceData.groupBy(groupColumns: _*).agg(exprs.head, exprs.tail: _*)

在必要时添加aliases