【发布时间】:2019-10-28 17:56:23
【问题描述】:
假设我有 4 个 spark 指令块,分别表示为 Dataframe A、B、C 和 D。
数据框有依赖关系:
- C 要求完成 A 和 B
- D 要求 B 完成
现在我想同时保存 C 和 D 输出。 为了不重新计算 B 两次,我使用了
B = B.cache()
事实上,如果我先保存 C,我会让 A 和 B 并行运行,然后是 C,然后当我保存 D 时,它只需要计算 D,这在某种程度上是足够好的调度。
现在的问题是:如果不是缓存 B,而是像这样将其保存在镶木地板中
B.write.mode("overwrite").parquet(CacheLocation)
B = spark.read.parquet(CacheLocation)
整个运行速度比使用 B.cache() 至少快 10 倍(比在 B 上完全不使用任何缓存快 30 倍)
上述方法的问题是现在我不能同时运行 A 和 B,除非我使用 Futures 并决定缓存 A。我的性能非常受限,我的真实代码有超过 50 个块/4 个输出要同步,除非我可以使用 Spark 的默认调度,否则这是一场噩梦。
我想知道是否有任何方法可以覆盖 Spark 的缓存方法以保存为镶木地板而不是内存?或者有什么方法可以在 Spark 自己的调度程序中添加这种 parquet 缓存?
伪代码说明:
场景 1:使用缓存,整个运行 300 秒,但 A 和 B 并行运行
val A = parquet.read(A).withColumn(NewA,FormulaA)
val B = parquet.read(B).withColumn(NewB,FormulaC).cache
val C = A.join(B, FormulaA===FormulaC,left)
val D = B.withColumn(NewD, FormulaD)
C.write.mode("overwrite").parquet(SaveCLocation)
D.write.mode("overwrite").parquet(SaveDLocation)
场景2:使用parquet作为缓存,整个运行30s,但A和B串行运行
val A = parquet(A).withColumn(NewA,FormulaA)
var B = parquet(B).withColumn(NewB,FormulaC)
B.write.parquet(CacheLocation)
B = spark.read.parquet(CacheLocation)
val C = A.join(B, FormulaA===FormulaC,left)
val D = B.withColumn(NewD, FormulaD)
C.write.parquet(SaveCLocation)
D.write.parquet(SaveDLocation)
【问题讨论】:
-
你写的东西对我来说没有意义。 IO 始终是最慢的解决方案。我怀疑你体验到它快 10 倍的原因是因为当你调用 C 和 D 时 B 数据帧实际上并没有被缓存。请记住,Spark 进行惰性评估,所以是否有动作(比如
count)强制评估在 C 和 D 中使用 B 之前(并因此缓存)它?如果你能提供更多的代码,也许可以? -
我可以看到缓存的行为就像我在 SparkWebUI 上提到的那样,所以我很确定这不是问题。当我保存 C 时,我会执行 C.write.mode("overwrite").parquet(SaveLocation) 并且它是第一次评估 B。 parquet 格式一定有一些东西可以让我的所有操作(过滤器、连接和 if/then 瀑布)比缓存的数据帧快得多。我的代码太复杂,无法放在这里,如果我的情况有希望,我可以尝试设置可以重现问题的虚拟代码。
-
write将强制评估您的 B 数据框。如果您只使用cache()并且在在 D 和 C 中使用它之前没有做任何事情来强制 B 被评估(并因此被缓存),Spark 在必须开始评估 D 和 C 之前不会评估(或缓存)B,这可以解释您的性能差异。 -
另外,如果你能提供你使用的代码,这真的很有帮助,这样我们就可以准确地看到你的评估顺序和你对
cache和write的使用。 .. -
我添加了代码说明
标签: scala apache-spark lazy-evaluation parquet