This topic describes how to configure and use the dynamic resource allocation (DRA) feature in Spark to maximize cluster resource utilization, reduce idle resources, and improve job flexibility and overall system performance.
What is dynamic resource allocation?
Dynamic resource allocation (DRA) is a feature in Spark that adjusts the computing resources a job uses based on its workload. If an Executor is idle for an extended period, the Driver automatically releases it and returns the resources to the cluster. Conversely, if tasks are queued for too long, the Driver requests more Executors to run them. DRA helps Spark adapt to fluctuating workloads, which prevents long runtimes due to under-provisioning and resource waste from over-provisioning.
Allocation policy
When you enable DRA, the Driver requests additional Executors if tasks are pending. If tasks remain pending for longer than spark.dynamicAllocation.schedulerBacklogTimeout (default: 1s), the Driver starts requesting Executors in rounds. If there are still pending tasks after each spark.dynamicAllocation.sustainedSchedulerBacklogTimeout (default: 1s), the Driver continues to request more Executors. The number of Executors requested in each round increases exponentially: 1, 2, 4, 8, and so on.
Removal policy
If an Executor remains idle for longer than spark.dynamicAllocation.executorIdleTimeout (default: 60s), the Driver automatically releases it to optimize resource utilization.
Enable dynamic resource allocation
The Spark DRA mechanism is available in Standalone, YARN, Mesos, and Kubernetes run modes but is disabled by default. To enable DRA, in addition to setting spark.dynamicAllocation.enabled to true, you must also configure the following options:
-
Enable External Shuffle Service (ESS): Set
spark.shuffle.service.enabledtotrueand configure an External Shuffle Service on each worker node. -
Enable shuffle tracking: Set
spark.dynamicAllocation.shuffleTracking.enabledtotrue. Spark tracks the location and status of shuffle data to prevent data loss and ensure it can be recomputed if needed. -
Enable node decommissioning: Set
spark.decommission.enabledandspark.storage.decommission.shuffleBlocks.enabledtotrue. This setting prompts Spark to proactively migrate shuffle blocks from a node that is being decommissioned to other available nodes. -
Configure a ShuffleDataIO plugin: Specify a ShuffleDataIO plugin class for
spark.shuffle.sort.io.plugin.classto customize shuffle I/O operations, allowing you to write shuffle data to different storage systems.
Configuration varies by mode:
-
Spark Standalone mode: Configure Option 1 only.
-
Spark on Kubernetes mode:
-
Without using Celeborn as a Remote Shuffle Service (RSS): Set
spark.dynamicAllocation.shuffleTracking.enabledtotrue(Option 2). -
When using Celeborn as an RSS:
-
For Spark 3.5.0 or later: Set
spark.shuffle.sort.io.plugin.classtoorg.apache.spark.shuffle.celeborn.CelebornShuffleDataIO(Option 4). -
For Spark versions earlier than 3.5.0: Apply the corresponding patch to Spark. No additional configuration is required. For more information, see Support Spark Dynamic Allocation.
-
For Spark 3.4.0 or later: We recommend setting
spark.dynamicAllocation.shuffleTracking.enabledtofalseto ensure idle Executors are released promptly.
-
-
This guide demonstrates configuring DRA in Spark on Kubernetes mode for scenarios with and without Celeborn as an RSS.
Prerequisites
-
You have deployed the ack-spark-operator component.
NoteThis example uses
spark.jobNamespaces=["spark"]during deployment. To use a different namespace, modify the namespace field. -
You have deployed the ack-spark-history-server component.
NoteThis example creates a persistent volume (PV) named
nas-pvand a persistent volume claim (PVC) namednas-pvc, and configures the Spark History Server to read Spark event logs from the/spark/event-logspath on NAS. -
(Optional) You have deployed the ack-celeborn component.
Procedure
Without Celeborn
In this Spark 3.5.4 example, to enable DRA without a Remote Shuffle Service (RSS), set the following parameters:
-
Enable dynamic resource allocation: Set
spark.dynamicAllocation.enabledto"true". -
Enable shuffle tracking, which allows DRA without an ESS: Set
spark.dynamicAllocation.shuffleTracking.enabledto"true".
-
Create a SparkApplication manifest file named
spark-pagerank-dra.yamlwith the following content:apiVersion: sparkoperator.k8s.io/v1beta2 kind: SparkApplication metadata: name: spark-pagerank-dra namespace: spark spec: type: Scala mode: cluster # Replace <SPARK_IMAGE> with your Spark image. image: <SPARK_IMAGE> mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.12-3.5.4.jar mainClass: org.apache.spark.examples.SparkPageRank arguments: # Replace <OSS_BUCKET> with your OSS bucket name. - oss://<OSS_BUCKET>/data/pagerank_dataset.txt - "10" sparkVersion: 3.5.4 hadoopConf: # Allow access to OSS data by using the oss:// format. fs.AbstractFileSystem.oss.impl: org.apache.hadoop.fs.aliyun.oss.OSS fs.oss.impl: org.apache.hadoop.fs.aliyun.oss.AliyunOSSFileSystem # The OSS endpoint. Replace <OSS_ENDPOINT> with your OSS endpoint. # For example, the internal endpoint for OSS in the China (Beijing) region is oss-cn-beijing-internal.aliyuncs.com fs.oss.endpoint: <OSS_ENDPOINT> # Read access credentials for OSS from environment variables. fs.oss.credentials.provider: com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider sparkConf: # ==================== # Event Logs # ==================== spark.eventLog.enabled: "true" spark.eventLog.dir: file:///mnt/nas/spark/event-logs # ==================== # Dynamic Resource Allocation # ==================== # Enable dynamic resource allocation. spark.dynamicAllocation.enabled: "true" # Enable shuffle tracking to allow dynamic resource allocation without relying on an ESS. spark.dynamicAllocation.shuffleTracking.enabled: "true" # The initial number of Executors. spark.dynamicAllocation.initialExecutors: "1" # The minimum number of Executors. spark.dynamicAllocation.minExecutors: "0" # The maximum number of Executors. spark.dynamicAllocation.maxExecutors: "5" # The idle timeout for an Executor. If the timeout is exceeded, the Executor is released. spark.dynamicAllocation.executorIdleTimeout: 60s # The idle timeout for an Executor that has cached data blocks. The default is infinity, which means it is not released. # spark.dynamicAllocation.cachedExecutorIdleTimeout: # When pending tasks exceed this timeout, more Executors are requested. spark.dynamicAllocation.schedulerBacklogTimeout: 1s # The interval at which subsequent rounds of Executor requests are made. spark.dynamicAllocation.sustainedSchedulerBacklogTimeout: 1s driver: cores: 1 coreLimit: 1200m memory: 512m envFrom: - secretRef: name: spark-oss-secret volumeMounts: - name: nas mountPath: /mnt/nas serviceAccount: spark-operator-spark executor: cores: 1 coreLimit: "2" memory: 8g envFrom: - secretRef: name: spark-oss-secret volumeMounts: - name: nas mountPath: /mnt/nas volumes: - name: nas persistentVolumeClaim: claimName: nas-pvc restartPolicy: type: NeverNoteThe Spark image used in this job must include the Hadoop OSS SDK dependency. You can use the following Dockerfile to build the image and push it to your own image repository:
ARG SPARK_IMAGE=spark:3.5.4 FROM ${SPARK_IMAGE} # Add dependency for Hadoop Aliyun OSS support ADD --chown=spark:spark --chmod=644 https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-aliyun/3.3.4/hadoop-aliyun-3.3.4.jar ${SPARK_HOME}/jars ADD --chown=spark:spark --chmod=644 https://repo1.maven.org/maven2/com/aliyun/oss/aliyun-sdk-oss/3.17.4/aliyun-sdk-oss-3.17.4.jar ${SPARK_HOME}/jars ADD --chown=spark:spark --chmod=644 https://repo1.maven.org/maven2/org/jdom/jdom2/2.0.6.1/jdom2-2.0.6.1.jar ${SPARK_HOME}/jars -
Run the following command to submit the Spark job.
kubectl apply -f spark-pagerank-dra.yamlExpected output:
sparkapplication.sparkoperator.k8s.io/spark-pagerank-dra created -
View the Driver logs.
kubectl logs -n spark spark-pagerank-dra-driver | grep -a2 -b2 "Going to request"Expected output:
3544-25/01/16 03:26:04 INFO SparkKubernetesClientFactory: Auto-configuring K8S client using current context from users K8S config file 3674-25/01/16 03:26:06 INFO Utils: Using initial executors = 1, max of spark.dynamicAllocation.initialExecutors, spark.dynamicAllocation.minExecutors and spark.executor.instances 3848:25/01/16 03:26:06 INFO ExecutorPodsAllocator: Going to request 1 executors from Kubernetes for ResourceProfile Id: 0, target: 1, known: 0, sharedSlotFromPendingPods: 2147483647. 4026-25/01/16 03:26:06 INFO ExecutorPodsAllocator: Found 0 reusable PVCs from 0 PVCs 4106-25/01/16 03:26:06 INFO BasicExecutorFeatureStep: Decommissioning not enabled, skipping shutdown script -- 10410-25/01/16 03:26:15 INFO TaskSetManager: Starting task 0.0 in stage 0.0 (TID 0) (192.168.95.190, executor 1, partition 0, PROCESS_LOCAL, 9807 bytes) 10558-25/01/16 03:26:15 INFO BlockManagerInfo: Added broadcast_1_piece0 in memory on 192.168.95.190:34327 (size: 12.5 KiB, free: 4.6 GiB) 10690:25/01/16 03:26:16 INFO ExecutorPodsAllocator: Going to request 1 executors from Kubernetes for ResourceProfile Id: 0, target: 2, known: 1, sharedSlotFromPendingPods: 2147483647. 10868-25/01/16 03:26:16 INFO ExecutorAllocationManager: Requesting 1 new executor because tasks are backlogged (new desired total will be 2 for resource profile id: 0) 11030-25/01/16 03:26:16 INFO ExecutorPodsAllocator: Found 0 reusable PVCs from 0 PVCsThe log shows that the Driver requested one Executor at 03:26:06 and another at 03:26:16.
-
Access the Spark History Server to view the job. For instructions, see Access the Spark History Server Web UI.
The job's event timeline also shows that two Executors were created sequentially.
With Celeborn
In this Spark 3.5.4 example, to enable DRA when using Celeborn as an RSS, set the following parameters:
-
Enable dynamic resource allocation: Set
spark.dynamicAllocation.enabledto"true". -
Configure support for dynamic resource allocation (for Spark 3.5.0 or later): Set
spark.shuffle.sort.io.plugin.classtoorg.apache.spark.shuffle.celeborn.CelebornShuffleDataIO. -
Ensure idle Executors are released promptly: Set
spark.dynamicAllocation.shuffleTracking.enabledto"false".
-
Create a SparkApplication manifest file named
spark-pagerank-celeborn-dra.yamlwith the following content:apiVersion: sparkoperator.k8s.io/v1beta2 kind: SparkApplication metadata: name: spark-pagerank-celeborn-dra namespace: spark spec: type: Scala mode: cluster # Replace <SPARK_IMAGE> with your Spark image. This image must include dependencies for Hadoop OSS and Celeborn. image: <SPARK_IMAGE> mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.12-3.5.4.jar mainClass: org.apache.spark.examples.SparkPageRank arguments: # Replace <OSS_BUCKET> with your OSS bucket name. - oss://<OSS_BUCKET>/data/pagerank_dataset.txt - "10" sparkVersion: 3.5.4 hadoopConf: # Allow access to OSS data by using the oss:// format. fs.AbstractFileSystem.oss.impl: org.apache.hadoop.fs.aliyun.oss.OSS fs.oss.impl: org.apache.hadoop.fs.aliyun.oss.AliyunOSSFileSystem # The OSS endpoint. Replace <OSS_ENDPOINT> with your OSS endpoint. # For example, the internal endpoint for OSS in the China (Beijing) region is oss-cn-beijing-internal.aliyuncs.com fs.oss.endpoint: <OSS_ENDPOINT> # Read access credentials for OSS from environment variables. fs.oss.credentials.provider: com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider sparkConf: # ==================== # Event Logs # ==================== spark.eventLog.enabled: "true" spark.eventLog.dir: file:///mnt/nas/spark/event-logs # ==================== # Celeborn # Ref: https://github.com/apache/celeborn/blob/main/README.md#spark-configuration # ==================== # Shuffle manager class name changed in 0.3.0: # before 0.3.0: `org.apache.spark.shuffle.celeborn.RssShuffleManager` # since 0.3.0: `org.apache.spark.shuffle.celeborn.SparkShuffleManager` spark.shuffle.manager: org.apache.spark.shuffle.celeborn.SparkShuffleManager # Must use kryo serializer because java serializer do not support relocation. spark.serializer: org.apache.spark.serializer.KryoSerializer # This must be configured based on the number of Celeborn master replicas. spark.celeborn.master.endpoints: celeborn-master-0.celeborn-master-svc.celeborn.svc.cluster.local,celeborn-master-1.celeborn-master-svc.celeborn.svc.cluster.local,celeborn-master-2.celeborn-master-svc.celeborn.svc.cluster.local # options: hash, sort # Hash shuffle writer uses (partition count) * (celeborn.push.buffer.max.size) * (spark.executor.cores) memory. # Sort shuffle writer uses less memory than hash shuffle writer. If your shuffle partition count is large, try using the sort hash writer. spark.celeborn.client.spark.shuffle.writer: hash # We recommend setting `spark.celeborn.client.push.replicate.enabled` to true to enable server-side data replication. # If you have only one worker, this setting must be false. # If your Celeborn is using HDFS, it's recommended to set this to false. spark.celeborn.client.push.replicate.enabled: "false" # Support for Spark AQE is only tested under Spark 3. spark.sql.adaptive.localShuffleReader.enabled: "false" # We recommend enabling AQE support to gain better performance. spark.sql.adaptive.enabled: "true" spark.sql.adaptive.skewJoin.enabled: "true" # For Spark 3.5.0 or later, configure this option to support dynamic resource allocation. spark.shuffle.sort.io.plugin.class: org.apache.spark.shuffle.celeborn.CelebornShuffleDataIO spark.executor.userClassPathFirst: "false" # ==================== # Dynamic Resource Allocation # Ref: https://spark.apache.org/docs/latest/job-scheduling.html#dynamic-resource-allocation # ==================== # Enable dynamic resource allocation. spark.dynamicAllocation.enabled: "true" # Enable shuffle tracking to allow dynamic resource allocation without relying on an ESS. # When using Celeborn as an RSS with Spark 3.4.0 or later, we strongly recommend you disable this option. spark.dynamicAllocation.shuffleTracking.enabled: "false" # The initial number of Executors. spark.dynamicAllocation.initialExecutors: "1" # The minimum number of Executors. spark.dynamicAllocation.minExecutors: "0" # The maximum number of Executors. spark.dynamicAllocation.maxExecutors: "5" # The idle timeout for an Executor. If the timeout is exceeded, the Executor is released. spark.dynamicAllocation.executorIdleTimeout: 60s # The idle timeout for an Executor that has cached data blocks. The default is infinity, which means it is not released. # spark.dynamicAllocation.cachedExecutorIdleTimeout: # When pending tasks exceed this timeout, more Executors are requested. spark.dynamicAllocation.schedulerBacklogTimeout: 1s # The interval at which subsequent rounds of Executor requests are made. spark.dynamicAllocation.sustainedSchedulerBacklogTimeout: 1s driver: cores: 1 coreLimit: 1200m memory: 512m envFrom: - secretRef: name: spark-oss-secret volumeMounts: - name: nas mountPath: /mnt/nas serviceAccount: spark-operator-spark executor: cores: 1 coreLimit: "1" memory: 4g envFrom: - secretRef: name: spark-oss-secret volumeMounts: - name: nas mountPath: /mnt/nas volumes: - name: nas persistentVolumeClaim: claimName: nas-pvc restartPolicy: type: NeverNoteThe Spark image used in the preceding example job must include dependencies for the Hadoop OSS SDK and Celeborn. You can use the following Dockerfile to build the image and push it to your own image repository:
ARG SPARK_IMAGE=spark:3.5.4 FROM ${SPARK_IMAGE} # Add dependency for Hadoop Aliyun OSS support ADD --chown=spark:spark --chmod=644 https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-aliyun/3.3.4/hadoop-aliyun-3.3.4.jar ${SPARK_HOME}/jars ADD --chown=spark:spark --chmod=644 https://repo1.maven.org/maven2/com/aliyun/oss/aliyun-sdk-oss/3.17.4/aliyun-sdk-oss-3.17.4.jar ${SPARK_HOME}/jars ADD --chown=spark:spark --chmod=644 https://repo1.maven.org/maven2/org/jdom/jdom2/2.0.6.1/jdom2-2.0.6.1.jar ${SPARK_HOME}/jars # Add dependency for Celeborn ADD --chown=spark:spark --chmod=644 https://repo1.maven.org/maven2/org/apache/celeborn/celeborn-client-spark-3-shaded_2.12/0.5.3/celeborn-client-spark-3-shaded_2.12-0.5.3.jar ${SPARK_HOME}/jars -
Run the following command to submit the Spark job.
kubectl apply -f spark-pagerank-celeborn-dra.yamlExpected output:
sparkapplication.sparkoperator.k8s.io/spark-pagerank-celeborn-dra created -
View the Driver logs.
kubectl logs -n spark spark-pagerank-celeborn-dra-driver | grep -a2 -b2 "Going to request"Expected output:
3544-25/01/16 03:51:28 INFO SparkKubernetesClientFactory: Auto-configuring K8S client using current context from users K8S config file 3674-25/01/16 03:51:30 INFO Utils: Using initial executors = 1, max of spark.dynamicAllocation.initialExecutors, spark.dynamicAllocation.minExecutors and spark.executor.instances 3848:25/01/16 03:51:30 INFO ExecutorPodsAllocator: Going to request 1 executors from Kubernetes for ResourceProfile Id: 0, target: 1, known: 0, sharedSlotFromPendingPods: 2147483647. 4026-25/01/16 03:51:30 INFO ExecutorPodsAllocator: Found 0 reusable PVCs from 0 PVCs 4106-25/01/16 03:51:30 INFO CelebornShuffleDataIO: Loading CelebornShuffleDataIO -- 11796-25/01/16 03:51:41 INFO TaskSetManager: Starting task 0.0 in stage 0.0 (TID 0) (192.168.95.163, executor 1, partition 0, PROCESS_LOCAL, 9807 bytes) 11944-25/01/16 03:51:42 INFO BlockManagerInfo: Added broadcast_1_piece0 in memory on 192.168.95.163:37665 (size: 13.3 KiB, free: 2.1 GiB) 12076:25/01/16 03:51:42 INFO ExecutorPodsAllocator: Going to request 1 executors from Kubernetes for ResourceProfile Id: 0, target: 2, known: 1, sharedSlotFromPendingPods: 2147483647. 12254-25/01/16 03:51:42 INFO ExecutorAllocationManager: Requesting 1 new executor because tasks are backlogged (new desired total will be 2 for resource profile id: 0) 12416-25/01/16 03:51:42 INFO ExecutorPodsAllocator: Found 0 reusable PVCs from 0 PVCsThe log shows that the Driver requested an Executor at
03:51:30and another at03:51:42. -
Access the Spark History Server to view the job. For instructions, see Access the Spark History Server Web UI.
The job's event timeline also shows that two Executors were created sequentially.