是否应该缓存转换依赖关系图上的共同祖先?

时间:2017-04-11 11:20:02

标签: apache-spark rdd

在完成方案问题之前,我相对较新,甚至可能是错误的,所以请随意跳过阅读并指出你发现我在概念上错了,谢谢!

想象一下像这样的驱动程序代码:

val A = ... (some transformation)
val B = A.filter( fun1 ) 
val C = A.filter( fun2 ) 
...
B.someAction()... //do sth with B
...
C.someAction()... //do sth with C

转换RDD B和C都依赖于A,它本身可能是一个复杂的转换。 A会被计算两次吗?我认为它会因为火花无法做任何 inter - 转换,对吧? Spark在优化一次转换执行时非常聪明,因为可以对其中的捆绑任务进行彻底分析。例如,在 B.someAction 之后但在 C.someAction 之前可能会发生某些状态变化,这可能会影响A的值,因此重新计算变得必要。进一步的例子可能会发生这样的事情:

val arr = Array(...)
val A = sc.parallelize(...).flatMap(e => arr.map(_ * e)) //now A depends on some local array

... //B and C stays the same as above

B.someAction()
...
arr(i) = arr(i) + 10  //local state modified
...
C.someAction() //should A be recomputed? YES

这很容易验证,所以我做了一个快速实验,结果支持我的推理。

然而,如果B和C只是独立地依赖于A并且没有上述其他逻辑存在那么程序员或某个工具可以静态地分析代码并且说他们在A上添加缓存是可行的,这样它就不会不必要地重新计算!但是火花对此无能为力,有时甚至很难让人类做出决定:

val A = ... (some transformation)
var B = A.filter( fun1 ) 
var C: ??? = null
var D: ??? = null

if (cond) {
  //now whether multiple dependencies exist is runtime determined
  C = A.filter( fun2 ) 
  D = A.filter( fun3 )
}

B.someAction()... //do sth with B

if (cond) {
  C.someAction()... //do sth with C
  D.someAction()... //do sth with D
}

如果条件为真,那么缓存A很有吸引力,但是直到运行时才会知道。我知道这是一个人为的蹩脚的例子,但这些已经是简化的模型,在实践中事​​情会变得更加复杂,依赖关系可能会很长并且隐含在模块中,因此我的问题是处理这类问题的一般原则是什么。应该何时缓存转换依赖图上的共同祖先(假设内存不是问题)?

我想听听总是遵循功能性编程范式做火花的事情,或者总是缓存它们,如果可以的话,我可能不需要另外的情况:

val A = ... (some transformation)
val B = A.filter( fun1 ) 
val C = A.filter( fun2 ) 
...
B.join(C).someAction()

B和C都依赖于A而不是分别调用两个动作,它们被连接起来形成一个单一的转换。这一次,我相信火花足够聪明,只计算一次A.尚未找到正确的运行和检查方法,但在Web UI DAG中应该很明显。更进一步,我认为spark甚至可以将两个过滤器操作减少到A上的一次遍历,同时获得B和C.这是真的吗?

1 个答案:

答案 0 :(得分:1)

这里要解开很多东西。

  

转换RDD B和C都依赖于A,它本身可能是一个复杂的转换。 A会被计算两次吗?我认为它会因为火花不能做任何相互转换的事情,对吗?

是的,它将被计算两次,除非您致电A.cache()A.persist(),在这种情况下,它只会被计算一次。

  

例如,在B.someAction之后但在C.someAction之前可能会发生某些状态变化,这可能会影响A的值,因此需要重新计算

不,这不正确,A是不可变的,因此它的状态不能改变。 BC也是不可变的RDD,代表A的转换。

sc.parallelize(...).flatMap(e => arr.map(_ * e)) //now A depends on some local array

不,它不依赖于本地数组,它是一个包含(驱动程序)本地数组元素副本的不可变RDD。如果数组发生更改,A不会更改。要获得该行为,您必须var A = sc. parallelize(...),然后在本地数组更改A = sc.paralellize(...)时再次设置A.在这种情况下,A未被“更新”,它将被本地数组的新RDD表示替换,因此A的任何缓存版本都无效。

您发布的后续示例受益于缓存A。再次因为RDD是不可变的。