Spark RDD:多次reducebykey或只有一次

时间:2016-06-08 04:54:31

标签: scala performance apache-spark rdd

我的代码如下:

// make a rd according to an id
def makeRDD(id:Int, data:RDD[(VertexId, Double)]):RDD[(Long, Double)] = { ... }  
val data:RDD[(VertexId, Double)] = ... // loading from hdfs
val idList = (1 to 100)
val rst1 = idList.map(id => makeRDD(id, data)).reduce(_ union _).reduceByKey(_+_)
val rst2 = idList.map(id => makeRDD(id, data)).reduce((l,r) => (l union r).reduceByKey(_+_))

rst1和rst2得到样本结果。我认为rst1需要更多内存(100次),但只需要一次reduceByKey转换;但是,rst2需要更少的内存,但更多的reduceByKey转换(99次)。那么,这是时间和空间权衡的游戏吗?

我的问题是:我上面的分析是否正确,或者Spark会在内部以相同的方式翻译动作吗?

P.S。:rst1 union all sub rdd然后reduceByKey,其中reduceByKey 之外减少。 rst2逐个reduceByKey,其中reduceByKey 里面减少。

1 个答案:

答案 0 :(得分:3)

长话短说两种解决方案效率相对较低,但第二种解决方案比第一种解决方案更差。

让我们回答最后一个问题。对于低级RDD API,只有两种类型的全局自动优化(相反):

  • 使用显式或隐式缓存的任务结果,而不是重新计算完整的血统
  • 将不需要随机播放的多个转换合并为一个ShuffleMapStage

其他所有内容都是定义DAG的顺序转换。这与更具限制性的高级DatasetDataFrame)API形成对比,后者对转换进行了具体假设,并对执行计划执行全局优化。

关于你的代码。当您应用迭代union时,第一个解决方案的最大问题是不断增长的谱系。它使得一些事情,例如故障恢复成本高昂,并且由于RDD是递归定义的,因此可能会因StackOverflow异常而失败。不太严重的副作用是越来越多的分区在随后的减少*中似乎没有得到补偿。您可以在我对Stackoverflow due to long RDD Lineage的回答中找到更详细的解释,但您真正需要的是这样一个union

sc.union(idList.map(id => makeRDD(id, data))).reduceByKey(_+_)

这实际上是一种最佳解决方案,假设您应用了真正的减少功能。

第二种解决方案显然遇到了同样的问题,然而它变得更糟。虽然第一种方法只需要一个shuffle的两个阶段,但这需要对每个RDD进行随机播放。由于分区数量在增长,并且您使用默认HashPartitioner,因此每个数据都必须多次写入磁盘,并且很可能多次在网络上进行洗牌。忽略低级别计算,每个记录都会混洗 O(N)次,其中N是您合并的RDD数。

关于内存使用情况,如果不了解更多关于数据分布的情况并不明显,但在最坏的情况下,第二种方法可能表现出明显更糟糕的行为。

如果+适用于常量空间,则减少的唯一要求是存储地图侧合并的结果的散列图。由于分区作为数据流处理而不将完整内容读入内存,这意味着每个任务的总内存大小将与唯一键的数量成比例,而不是数据量。由于第二种方法需要更多任务,因此总体内存使用率将高于第一种情况。平均而言,由于数据部分组织可能稍微好一点,但不太可能补偿额外成本。

*如果您想了解它如何影响整体性能,您可以看到Spark iteration time increasing exponentially when using join这是一个稍微不同的问题,但应该让您知道为什么控制分区数量很重要。