我有一个基本上这样做的火花程序:
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个阶段。
答案 0 :(得分:0)
缓存不会减少阶段,它不会每次都重新计算阶段。
在第一次迭代中,在阶段的“输入大小”中,您可以看到数据来自Hadoop,并且它读取了随机输入。在后续迭代中,数据来自内存,不再有随机输入。此外,执行时间大大减少。
每当必须编写shuffle时,都会创建新的映射阶段,例如在分区更改时,在您的情况下向RDD添加密钥。
答案 1 :(得分:0)
这里的问题是调用cache
是懒惰的。在触发操作并评估RDD之前,不会缓存任何内容。所有调用都在RDD中设置一个标志,表示在评估时应该缓存它。
Unpersist立即生效。它清除标志,指示应该缓存RDD并开始清除缓存中的数据。由于您在应用程序结束时只有一个操作,这意味着在评估任何RDD时,Spark不会看到它们中的任何一个都应该保留!
我同意这是令人惊讶的行为。一些Spark库(包括GraphX中的PageRank实现)解决这个问题的方法是明确实现对cache
和unpersist
的调用之间的每个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(...)
}