You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kyuubi.apache.org by ul...@apache.org on 2023/03/30 07:39:57 UTC

[kyuubi] branch master updated: [KYUUBI #4636] Improve eagerly kill redundant executors

This is an automated email from the ASF dual-hosted git repository.

ulyssesyou pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kyuubi.git


The following commit(s) were added to refs/heads/master by this push:
     new 97aedf504 [KYUUBI #4636] Improve eagerly kill redundant executors
97aedf504 is described below

commit 97aedf50488b86187fb14ae92c3d380ddc2c3749
Author: ulysses-you <ul...@gmail.com>
AuthorDate: Thu Mar 30 15:39:44 2023 +0800

    [KYUUBI #4636] Improve eagerly kill redundant executors
    
    ### _Why are the changes needed?_
    
    This pr improves the behavoir of kill redundant executors.
    1. support kill executors even if AQE can not optimize shuffle read. e.g., people call `.repartition(2)`
    2. fix a issue that avoid always kill executors which holds shuffle data
    
    ### _How was this patch tested?_
    test manually
    
    Closes #4636 from ulysses-you/kill-executors.
    
    Closes #4636
    
    19ac808d3 [ulysses-you] Improve eagerly kill redundant executors
    
    Authored-by: ulysses-you <ul...@gmail.com>
    Signed-off-by: ulyssesyou <ul...@apache.org>
---
 .../spark/sql/FinalStageResourceManager.scala      | 59 ++++++++++++----------
 1 file changed, 31 insertions(+), 28 deletions(-)

diff --git a/extensions/spark/kyuubi-extension-spark-3-3/src/main/scala/org/apache/spark/sql/FinalStageResourceManager.scala b/extensions/spark/kyuubi-extension-spark-3-3/src/main/scala/org/apache/spark/sql/FinalStageResourceManager.scala
index f568f9df8..2bf7ae6b7 100644
--- a/extensions/spark/kyuubi-extension-spark-3-3/src/main/scala/org/apache/spark/sql/FinalStageResourceManager.scala
+++ b/extensions/spark/kyuubi-extension-spark-3-3/src/main/scala/org/apache/spark/sql/FinalStageResourceManager.scala
@@ -64,41 +64,45 @@ case class FinalStageResourceManager(session: SparkSession)
       return plan
     }
 
-    val stage = findFinalRebalanceStage(plan)
-    if (stage.isEmpty) {
+    val stageOpt = findFinalRebalanceStage(plan)
+    if (stageOpt.isEmpty) {
       return plan
     }
 
     // Since we are in `prepareQueryStage`, the AQE shuffle read has not been applied.
     // So we need to apply it by self.
-    val shuffleRead = queryStageOptimizerRules.foldLeft(stage.get.asInstanceOf[SparkPlan]) {
+    val shuffleRead = queryStageOptimizerRules.foldLeft(stageOpt.get.asInstanceOf[SparkPlan]) {
       case (latest, rule) => rule.apply(latest)
     }
-    shuffleRead match {
+    val (targetCores, stage) = shuffleRead match {
       case AQEShuffleReadExec(stage: ShuffleQueryStageExec, partitionSpecs) =>
-        // The condition whether inject custom resource profile:
-        // - target executors < active executors
-        // - active executors - target executors > min executors
-        val numActiveExecutors = sc.getExecutorIds().length
-        val targetCores = partitionSpecs.length
-        val targetExecutors = (math.ceil(targetCores.toFloat / coresPerExecutor) * factor).toInt
-          .max(1)
-        val hasBenefits = targetExecutors < numActiveExecutors &&
-          (numActiveExecutors - targetExecutors) > minExecutors
-        if (hasBenefits) {
-          val shuffleId = stage.plan.asInstanceOf[ShuffleExchangeExec].shuffleDependency.shuffleId
-          val numReduce = stage.plan.asInstanceOf[ShuffleExchangeExec].numPartitions
-          // Now, there is only a final rebalance stage waiting to execute and all tasks of previous
-          // stage are finished. Kill redundant existed executors eagerly so the tasks of final
-          // stage can be centralized scheduled.
-          killExecutors(sc, targetExecutors, shuffleId, numReduce)
-        } else {
-          logInfo(s"Has no benefits to kill executors or inject custom resource profile, " +
-            s"active executors: $numActiveExecutors, min executor: $minExecutors, " +
-            s"target executors: $targetExecutors.")
-        }
-
+        (partitionSpecs.length, stage)
+      case stage: ShuffleQueryStageExec =>
+        // we can still kill executors if no AQE shuffle read, e.g., `.repartition(2)`
+        (stage.shuffle.numPartitions, stage)
       case _ =>
+        // it should never happen in current Spark, but to be safe do nothing if happens
+        logWarning("BUG, Please report to Apache Kyuubi community")
+        return plan
+    }
+    // The condition whether inject custom resource profile:
+    // - target executors < active executors
+    // - active executors - target executors > min executors
+    val numActiveExecutors = sc.getExecutorIds().length
+    val targetExecutors = (math.ceil(targetCores.toFloat / coresPerExecutor) * factor).toInt
+      .max(1)
+    val hasBenefits = targetExecutors < numActiveExecutors &&
+      (numActiveExecutors - targetExecutors) > minExecutors
+    logInfo(s"The snapshot of current executors view, " +
+      s"active executors: $numActiveExecutors, min executor: $minExecutors, " +
+      s"target executors: $targetExecutors, has benefits: $hasBenefits")
+    if (hasBenefits) {
+      val shuffleId = stage.plan.asInstanceOf[ShuffleExchangeExec].shuffleDependency.shuffleId
+      val numReduce = stage.plan.asInstanceOf[ShuffleExchangeExec].numPartitions
+      // Now, there is only a final rebalance stage waiting to execute and all tasks of previous
+      // stage are finished. Kill redundant existed executors eagerly so the tasks of final
+      // stage can be centralized scheduled.
+      killExecutors(sc, targetExecutors, shuffleId, numReduce)
     }
 
     plan
@@ -144,9 +148,8 @@ case class FinalStageResourceManager(session: SparkSession)
     // shuffle status.
     // We should evict executors by their alive time first and retain all of executors which
     // have better locality for shuffle block.
-    val numExecutorToKillWithNoShuffle = expectedNumExecutorToKill - executorToBlockSize.size
     executorsWithRegistrationTs.toSeq.sortBy(_._2).foreach { case (id, _) =>
-      if (executorIdsToKill.length < numExecutorToKillWithNoShuffle &&
+      if (executorIdsToKill.length < expectedNumExecutorToKill &&
         !executorToBlockSize.contains(id)) {
         executorIdsToKill.append(id)
       }