liuwei063608的专栏

刘伟的随笔

CheckPoint的一些探寻


由于上项目的模块计算部分依赖于spark,那么在spark的使用上,需要针对不同规模和形式的数据,都要能最大限度的做到数据变换,模型计算等计算的稳定性支持。这也是elemental目前急需优化的瓶颈所在。这里,我们针对下面的场景所遇到的问题进行一部分探讨:

在数据规模过大,无法cache到memory上

  1. DataFrame在transform多次后,进行action后,生成的Physical Plan过长
  2. DataFrame和RDD在transform多次后,进行action后,生成的DAG过长

理论

  • Cache
  1. 当我们对DataFrame进行cache操作时,我们会对到目前生成的DataFrame的logicalPlan进行一次执行,并将每个partition计算完的结果保存为CachedBatch的格式,最终保存到CachedData的List数组中。对应的RDD也变为PersistRDD格式。
  2. 而经过Cache的DataFrame,在后续的计算中,
  3. 正常情况下在数据规模不大的情况下,我们只需要对DataFrame和RDD进行cache操作,就可以解决前面提到的问题。当然我们也可以选择Cache到磁盘上来应对数据规模比较大的情况。
  • CheckPoint
  1. 当我们对RDD进行Checkpoint操作时,只是暂时在此加上标记,表明该RDD需要被CheckPoint。
  2. 而在后续进行action操作时,在runJob计算完RDD之后,才会进行doCheckpoint的活动,也就是具体RDD进行Checkpoint实际的过程。在这个过程中,RDD的生成过程实际上要进行第二次的计算。
  3. DataFrame在进行CheckPoint的操作中,默认参数eager为true,也就是对应的InternalRdd在checkpoint函数之后,会默认进行一次简单的count的action操作,这样就完成了DataFrame数据的checkpoint,当然后续还会清理掉相对应的前序依赖,以达到降低DAG和physicalPlan复杂度的目的。

探寻

测试步骤如下:

val df1 = df.withColumn
val df2 = df1.groupBy.sum
val df3 = df2.withColumn

我们用进行count的过程作为DAG与plan分析的样本(进行checkpoint的操作的,分别对df1,df2,df3进行checkpoint之后,在进行count过程)

1.DataFrame进行checkpoint对比
没有使用checkpoint的情况下,logicPlan变化为

Aggregate [count(1) AS count#22L]
+- Project [id#3, double#4, plusOne#5, (id#3 % 9) AS idType#10]
   +- LogicalRDD [id#3, double#4, plusOne#5]


Aggregate [count(1) AS count#41L]
+- Aggregate [idType#10], [idType#10, sum(cast(double#4 as bigint)) AS sum(double)#33L]
   +- Project [id#3, double#4, plusOne#5, (id#3 % 9) AS idType#10]
      +- LogicalRDD [id#3, double#4, plusOne#5]
      
      
Aggregate [count(1) AS count#56L]
+- Project [idType#10, sum(double)#33L, (cast(sum(double)#33L as double) / cast(10 as double)) AS rst#46]
   +- Aggregate [idType#10], [idType#10, sum(cast(double#4 as bigint)) AS sum(double)#33L]
      +- Project [id#3, double#4, plusOne#5, (id#3 % 9) AS idType#10]
         +- LogicalRDD [id#3, double#4, plusOne#5]

由于是依赖关系,出现上述情况是合理的。

那么,在使用checkpoint之后

Aggregate [count(1) AS count#83L]
+- Project [id#64, double#65, plusOne#66, (id#64 % 9) AS idType#71]
   +- LogicalRDD [id#64, double#65, plusOne#66]
   
   
Aggregate [count(1) AS count#108L]
+- Aggregate [idType#71], [idType#71, sum(cast(double#65 as bigint)) AS sum(double)#100L]
   +- LogicalRDD [id#64, double#65, plusOne#66, idType#71]
   
   
Aggregate [count(1) AS count#129L]
+- Project [idType#71, sum(double)#100L, (cast(sum(double)#100L as double) / cast(10 as double)) AS rst#119]
   +- LogicalRDD [idType#71, sum(double)#100L]

很显然,LogicalPlan得到了抑制。与此相对应的PhysicalPlan也会得到缩减。

DAG的变化,这里只枚举df3的过程就可以说明问题
test1-3
图1.没有checkpoint情况下,df3进行count的DAG
test2-3
图2.在df2进行checkpoint情况下,df3进行count的DAG

对比,可以知道Stage已经得到了减少(图1在PhysicalPlan优化后才为3个Stage,实际上LogicalPlan已经为4个Stage),而且图1是从最开的df走流程下来的,而图2是直接从前面一个df2的checkpoint点出来的。

2.RDD进行checkpoint对比
使用RDD进行上述类似的操作,DAG的缩减也是一致,这里我们可以看一下RDD的recursive dependencies信息对比

(4) MapPartitionsRDD[64] at map at AlexTestJob.scala:115 []
 |  ShuffledRDD[63] at groupBy at AlexTestJob.scala:115 []
 +-(4) MapPartitionsRDD[62] at groupBy at AlexTestJob.scala:115 []
    |  MapPartitionsRDD[61] at map at AlexTestJob.scala:109 []
    |  ParallelCollectionRDD[60] at parallelize at AlexTestJob.scala:106 []
(4) MapPartitionsRDD[71] at map at AlexTestJob.scala:141 []
 |  ReliableCheckpointRDD[72] at count at AlexTestJob.scala:147 []

这是rdd2过程后的是否使用checkpoint的toDebugString的对比

3.DataFrame在循环体进行checkpoint对比
这里我们采用下面的逻辑代码进行测试

var df
(0 until 5).foreach {idx=>
df = df.withColumn(s"addCol_$idx",df.col("id")+idx)
}

LogicalPlan对比分析

'Project [*, (id#97 + 4) AS idType_4#134]
+- Project [id#97, double#98, plusOne#99, idType_0#104, idType_1#110, idType_2#117, (id#97 + 3) AS idType_3#125]
   +- Project [id#97, double#98, plusOne#99, idType_0#104, idType_1#110, (id#97 + 2) AS idType_2#117]
      +- Project [id#97, double#98, plusOne#99, idType_0#104, (id#97 + 1) AS idType_1#110]
         +- Project [id#97, double#98, plusOne#99, (id#97 + 0) AS idType_0#104]
            +- LogicalRDD [id#97, double#98, plusOne#99]

每次迭代都进行checkpoint之后

'Project [*, (id#3 + 4) AS idType_4#75]
+- LogicalRDD [id#3, double#4, plusOne#5, idType_0#15, idType_1#27, idType_2#41, idType_3#57]

这样的缩减,对于在模型计算过程中,多次迭代缩减DAG过程都存在实际意义

4.checkpoint与cache(DISK_ONLY)
cache只保存在DISK_ONLY可以理解为localCheckpoint的过程

结论

  1. 无论cache还是checkpoint操作,本质上是部分保存中间结果,减少后续过程重复计算。cache更倾向于保存比较频繁使用的,数据规模比较小的数据,且保存在内存中意义更大一些。checkpoint则相对于言没有数据规模的限制。
  2. checkpoint一次,会进行2次计算,这是额外开销。
  3. cache到磁盘上,是1次计算,但该次cache的结果仅能在该driver上运行的程序调用。实际上在elemental中是符合使用的。只是需要考虑对应的driver所在的机器的磁盘空间是否足够。
  4. checkpoint到alluxio,算是方便统一管理。checkpoint更大的优势是在于SparkStream上的优势,具有可恢复性。
  5. 一个RDD无论是否被标记为checkpoint,只要进行过实际性质action操作之后,该RDD就会被标记为已经checkpoint。例如:
RDD.checkpoint
RDD.count

这样是可以成功checkpoint的,但是:

RDD.count
RDD.checkpoint
RDD.count

无法被checkpoint。因此,选择checkpoint,之后马上action。最佳方案为:

RDD.checkpoint
RDD.persist(DISK_ONLY)
阅读更多
个人分类: spark
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭