Arnaud Nauwynck created SPARK-60794:
---------------------------------------

             Summary: Allow YARN executor vCore request to differ from 
spark.executor.cores (add spark.yarn.executor.request.cores as for k8s)
                 Key: SPARK-60794
                 URL: https://issues.apache.org/jira/browse/SPARK-60794
             Project: Spark
          Issue Type: Improvement
          Components: YARN
    Affects Versions: 3.5.9, 4.0.5, 4.1.3, 4.1.4, 4.2.1, 4.3.0, 5.0.0
            Reporter: Arnaud Nauwynck


h3. Problem
On YARN, spark.executor.cores drives two things:

the vCores requested from YARN per executor container 
(YarnAllocator.createYarnResourceForResourceProfile)
the number of task slots per executor (--cores passed by ExecutorRunnable, 
which the driver registers as totalCores)

There is no way to decouple them. Workloads that are mostly I/O-bound (JDBC, 
REST, object-store calls) want many concurrent tasks per executor while 
reserving few YARN vCores. Today they must either over-reserve cluster CPU or 
under-use executors.

Kubernetes already supports this through 
spark.kubernetes.executor.request.cores. YARN has no equivalent.

h3. Proposal
Add a new optional config:
{code}
spark.yarn.executor.request.cores (int, optional, default = 
spark.executor.cores)
{code}

Used only to build the YARN container Resource request.
spark.executor.cores keeps defining task slots, --cores, and dynamic allocation 
sizing.
The value is capped at spark.executor.cores.
It applies to the default ResourceProfile only. Stage-level profiles keep their 
explicit cores.

Example:
{code}
spark.executor.cores=20
spark.yarn.executor.request.cores=5
{code}
This gives 20 concurrent tasks per executor while reserving 5 vCores per 
container.

h3. Implementation

deploy/yarn/config.scala: add the EXECUTOR_REQUEST_CORES ConfigEntry.
YarnAllocator.createYarnResourceForResourceProfile: use the request cores in 
Resource.newInstance(totalMem, cores).
No change to runAllocatedContainers, ExecutorRunnable, ResourceProfile or the 
driver-side scheduler.
Container matching (matchContainerToRequest) uses the stored 
rpIdToYarnResource, so it stays consistent.


h3. minimal patch:

in config.scala, add a new parameter
{code:java}
private[spark] val YARN_EXECUTOR_REQUEST_CORES =
  ConfigBuilder("spark.yarn.executor.request.cores")
    .doc("vcores requested from YARN per executor; defaults to 
spark.executor.cores")
    .version("3.x-custom")
    .intConf
    .checkValue(_ > 0, "must be > 0")
    .createOptional
{code}

in YarnAllocator.createYarnResourceForResourceProfile:
https://github.com/apache/spark/blob/master/resource-managers/yarn/src/main/scala/org/apache/spark/deploy/yarn/YarnAllocator.scala#L362

{code:java}
val sparkExecutorCores = resourcesWithDefaults.cores.get
val yarnRequestCores = sparkConf.get(YARN_EXECUTOR_REQUEST_CORES)
  .map(c => math.min(c, sparkExecutorCores))
  .getOrElse(sparkExecutorCores)
val resource = Resource.newInstance(totalMem, yarnRequestCores)
{code}

instead of 
{code:java}
val resource = Resource.newInstance(
    resourcesWithDefaults.totalMemMiB.toInt, resourcesWithDefaults.cores.get)
{code}





--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to