spark 3.2 reuse pvc 功能改造

背景

spark reuse pvc feature

PVC: PersistentVolumeClaim. A PersistentVolumeClaim (PVC) is a request for storage by a user. It is similar to a Pod. Pods consume node resources and PVCs consume PV resources. Pods can request specific levels of resources (CPU and Memory). Claims can request specific size and access modes (e.g., they can be mounted ReadWriteOnce, ReadOnlyMany or ReadWriteMany, see AccessModes).

spark 3.2 推出 Support shuffle data recovery on the reused PVCs 功能,在spark on k8s部署模式下为pod申请pvc,在pod异常退出时,新创建的executor 通过复用历史pvc来恢复shuffle数据。

因为我们公司spark 任务使用 aws spot 实例,每天都会有大量spark 任务因为节点回收导致task重算,从而导致执行时间过长、成本上升。因此,我们希望通过该特性减少 task 重算,通过复用 pvc 已经计算过的数据加速任务执行。

spark shuffle 过程

父RDD的每个分区都有可能被多个子RDD分区使用,子RDD分区通常对应父RDD所有分区,在这样依赖关系下会进行shuffle 操作。

shuffle write
ShuffleWriter 有 BypassMergeSortShuffleWriter、UnsafeShuffleWriter、SortShuffleWriter实现类,负责将shuffle 数据的写出。在 map端不进行聚合且分区数目小于 16777216时会使用 UnsafeShuffleWriter,UnsafeShuffleWriter 里面维护着一个 ShuffleExternalSorter 用来做外部排序。 主要阶段:

  1. 根据 Dependency RDD partition 数量创建相同个数的 shuffleMapTask。
  2. shuffleMapTask 分区读取数据,写入内存,内存满则按分区排序后溢出到磁盘,执行期间可能会产生多个溢出文件。
  1. shuffleMapTask 数据写入完成后,将多个溢出文件进行合并压缩,形成shuffle_shuffleId_mapId_0.data和shuffle_shuffleId_mapId_0.index 文件。


  1. 将 shuffle 数据的元数据信息 MapStatus (包含 task out locaition、size of the blocks indexed by reduce partition id 、unique task id for the task) 存储到 MapOutputTracker。

MapOutputTracker
用来记录stage map task 的输出路径。

操作流程:

  • registerShuffle:向 shuffleStatuses 注册shuffle,填充 partition 数量的 MapStatus,MapStatus 值为null。
  • addMapOutput: 注册 map output.
  • removeMapOutput: 移除 map output
  • updateMapOutput: 更新 map output
  • findMissingPartitions: 查找丢失的分区数量, numPartitions - numAvailableMapOutputs

shuffle read

  • 从 MapOutputTracker 获取 shuffleId 对应的 mapStatus,从 mapStatus 中找到当前Task要读的partition数据,如果某个mapStatus包含对应的partition数据则最终会记录到 Iterator[(BlockManagerId, Seq[(ShuffleBlockId(shuffleId, status.mapId, part), size, mapIndex)])]。 这个过程也会验证 mapStatus 的状态,如果status 已经设置为null, 则抛出 Missing an output location for shuffle xx partition xxx.
  • 由 BlockStoreShuffleReader 读取数据,主要流程:
    • 根据 location 从 Iterator 中分离出 fetchRemoteBlocks 、 fetchLocalBlocks 代表通过网络请求、本地加载来读取数据。
    • BlockManager 根据 ShuffleBlockId 中的信息通过读取 index file 分区偏移量,来读取对应的data file 分区数据,返回数据流。
    • 对数据流进行反序列化处理,进行下发。

spark 处理 spot 回收

spot instance 被回收时,运行在上边的executor pod 被 delete,spark 对应的处理逻辑。

  • shuffle write 阶段:
    • 正在运行的 task 抛出 executor lost error,当前运行的 task 被重新调度。
    • scheduler 会在 MapOutTrack 移除当前executor维护的元数据信息,scheduler 会重新调度 executorId上的所有task,重新生成shuffle 数据。
  • shuffle read 阶段:
    • 正在运行的task 抛出 FetchFailedException 异常。
    • scheduler 会重新调度当前 stage,调度当前stage 是检查 parents shuffle Stage 的 MapOutTrack 是否已经全部被填充,未填充则重新计算。

社区版

  • 设计目标
    不修改 Spark 调度逻辑情况下,尽可能从pvc中恢复shuffle数据,数据重新计算与恢复属于竞争关系。

假设 StageC 在Shuffle read阶段因为Executor Lost出现FetchFailed异常,Spark的DAGScheduler会从 StageA 重算丢失的数据,如果在重算过程中 StageB 的shuffle 数据恢复了,则直接跳过 StageB,重新调度 StageC 失败的 task。

  • 现有的功能

    • 创建executor pod时,新建/复用已有的pvc (pvc数量-pod数量=可复用pvc)
    • 启动executor时, 在shuffle write初始化时上报本地 shuffle元数据信息给MapOutTrack
  • 局限
    找了几个demo测试,发现基本没啥用,stage、task重算次数很多,且pvc创建明显多于pod数量。

    • 对pvc数量管理不准确,数据恢复准确性很低。
      • 社区版的pvc只会增加不会删除,在开启dynamicAllocation时,可能pvc数量远远大于pod数量,导致pvc被pod复用的准确性降低。
      • 计算可复用pvc的逻辑误差较大,有些pod已经退出,但是持有的pvc标记为不可复用。
    • 执行数据恢复操作的时间较晚
      • 在executor 执行shuffle write时才触发上报数据,可能会导致历史数据无法上报。
    • 查找shuffle 数据路径不正确
      • cluser 模式下 shuffle 数据位于 blockmgr- 开头的文件夹,当前查找spark-开头的文件夹。
    • 未修改调度逻辑,stage重算时间过长,task重算次数过多
      • Executor lost 则当前Executor计算过的task会重新计算一遍。
      • 在 Shuffle Read 阶段,Executor lost 会调度前一个Stage 和 当前Stage丢失的任务




定制化目标

  • 修改spark 调度逻辑,通过复用 PVC尽可能减少Stage、Task重算,降低Job的执行时间。
  • 对可用pvc的数量进行精确控制,提高pvc 被挂载的准确性。

实现

pvc数量精确控制

pod在创建时会从空闲的pvc中随机选择一个,如果pvc数量大于最终要运行的executor pod数量,可能会导致shuffle数据无法恢复。


  • pod 删除时,当前pod使用的pvc一起删除,并且在内存缓存已经删除的pvc名称,会进行 pvc 删除的操作
    • dynamicAllocation enable
    • ContainerCreating timeout
    • executor successed
  • 有 Terminating 状态的Pod时,延迟创建,确保pvc被释放。
  • 取 Available 、Bound 状态的pvc 进行 reuse。
  • 取 last executorPodsSnapshot 来精确计算可用pvc数量。

stage延迟调度


StageC 的task执行时抛出 FetchFailed 异常,DAGScheduler 会将 StageB 和 StageC 失败的task重新调度,调度 StageB时会调度 parent stageA 丢失的task。

  • 开启reuse pvc 且stage retry times < spark.stage.maxConsecutiveAttempts /2 只调度当前Stage ,且延迟 spark.retry.shuffle.stage.delay 调度。等待pvc数据恢复
  • stage retry times > spark.stage.maxConsecutiveAttempts /2 按原来逻辑计算

task 延迟失败重算

  • 开启reuse pvc 在处理executor lost 时不重算已经成功的task,在shuffle read阶段如果pvc数据还未恢复再重新计算。

executor 恢复shuffle数据前置

  • 在executor 初始化blockManger后,立即上报本地 shuffle 数据给MapOutTrack。

效果

问题

  • executor pod 创建时间过长,导致失败的 stage重新调度,即社区版流程。
  • 线上spark job 同时调度 pvc 处于 Terminating 状态很多。

你可能感兴趣的:(spark 3.2 reuse pvc 功能改造)