意外的火花缓存行为

时间:2015-06-14 23:46:46

标签: scala apache-spark rdd

我有一个基本上这样做的火花程序:

def foo(a: RDD[...], b: RDD[...]) = {
  val c = a.map(...)
  c.persist(StorageLevel.MEMORY_ONLY_SER)
  var current = b
  for (_ <- 1 to 10) {
    val next = some_other_rdd_ops(c, current)
    next.persist(StorageLevel.MEMORY_ONLY)
    current.unpersist()
    current = next
  }
  current.saveAsTextFile(...)
}

我看到的奇怪行为是与val c = a.map(...)对应的火花阶段发生了10次。我原本预计只会发生一次因为下一行的立即缓存,但事实并非如此。当我查看正在运行的作业的“存储”选项卡时,很少有c的分区被缓存。

此外,该阶段的10个副本立即显示为“活动”。与val next = some_other_rdd_ops(c, current)对应的舞台的10个副本显示为待定,并且它们大致交替执行。

我是否误解了如何让Spark缓存RDD?

编辑:这是一个包含重现此项目的程序的要点:https://gist.github.com/jfkelley/f407c7750a086cdb059c。它期望输入图形的边缘列表(具有边缘权重)。例如:

a   b   1000.0
a   c   1000.0
b   c   1000.0
d   e   1000.0
d   f   1000.0
e   f   1000.0
g   h   1000.0
h   i   1000.0
g   i   1000.0
d   g   400.0

要点的第31-42行对应于上面的简化版本。当我只期望1时,我得到了与第31行相对应的10个阶段。

2 个答案:

答案 0 :(得分:0)

缓存不会减少阶段,它不会每次都重新计算阶段。

在第一次迭代中,在阶段的“输入大小”中,您可以看到数据来自Hadoop,并且它读取了随机输入。在后续迭代中,数据来自内存,不再有随机输入。此外,执行时间大大减少。

每当必须编写shuffle时,都会创建新的映射阶段,例如在分区更改时,在您的情况下向RDD添加密钥。

答案 1 :(得分:0)

这里的问题是调用cache是懒惰的。在触发操作并评估RDD之前,不会缓存任何内容。所有调用都在RDD中设置一个标志,表示在评估时应该缓存它。

然而,

Unpersist立即生效。它清除标志,指示应该缓存RDD并开始清除缓存中的数据。由于您在应用程序结束时只有一个操作,这意味着在评估任何RDD时,Spark不会看到它们中的任何一个都应该保留!

我同意这是令人惊讶的行为。一些Spark库(包括GraphX中的PageRank实现)解决这个问题的方法是明确实现对cacheunpersist的调用之间的每个RDD。例如,在您的情况下,您可以执行以下操作:

def foo(a: RDD[...], b: RDD[...]) = {
  val c = a.map(...)
  c.persist(StorageLevel.MEMORY_ONLY_SER)
  var current = b
  for (_ <- 1 to 10) {
    val next = some_other_rdd_ops(c, current)
    next.persist(StorageLevel.MEMORY_ONLY)
    next.foreachPartition(x => {}) // materialize before unpersisting
    current.unpersist()
    current = next
  }
  current.saveAsTextFile(...)
}