[ 
https://issues.apache.org/jira/browse/SPARK-61258?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18141505#comment-18141505
 ] 

Purushotham Pushpavanthar commented on SPARK-61258:
---------------------------------------------------

Prototype run of the shape ladder (branch 
[SPARK-61258-ladder-prototype|https://github.com/pushpavanthar/spark/tree/SPARK-61258-ladder-prototype],
 results and scripts under {{dev/spip-61258-ladder-demo/}}).

*Setup:* KWOK fake cluster with one 16-cpu/64Gi node and two 8-cpu/32Gi nodes. 
Driver: {{spark.executor.cores=4}}, {{spark.executor.memory=4g}}, 
{{spark.executor.instances=8}} (32 cores wanted), 
{{spark.kubernetes.executor.shape.ladder=16,8,4}}, {{downshiftTimeout=30s}}.

*Observed:*
* t+0s: exec-1 (16 cores, 18022Mi) Running on big-16cpu; exec-2 (16 cores) 
Pending, scheduler message {{0/3 nodes are available: 3 Insufficient cpu}}.
* t+30s: allocator logs "moving the shape ladder down to 8 cores per executor", 
deletes exec-2 and requests two 8-core pods.
* t+51s: exec-3 and exec-4 (8 cores, 9011Mi each) Running on small-8cpu-a and 
small-8cpu-b.
* Final fill 16 + 8 + 8 = 32 cores = target; heap per core constant (1g/core 
plus 10% overhead on every rung).

A homogeneous 16-core setting would strand both 8-cpu nodes; a homogeneous 
4-core setting reaches the target with 8 JVMs instead of 3. Prototype scope: 
default profile only, downshift only (no upshift probe yet), no excess cleanup 
under a ladder.


> SPIP: Ratio-constrained heterogeneous executors on Kubernetes
> -------------------------------------------------------------
>
>                 Key: SPARK-61258
>                 URL: https://issues.apache.org/jira/browse/SPARK-61258
>             Project: Spark
>          Issue Type: New Feature
>          Components: Kubernetes
>    Affects Versions: 5.0.0
>            Reporter: Purushotham Pushpavanthar
>            Priority: Major
>
> h3. SPIP: Ratio-constrained heterogeneous executors on Kubernetes
> Full SPIP document: 
> https://docs.google.com/document/d/1PCTHXYIgzDQLmNO9KUjBxoAkG36z-P7J_t9SDvaPjzI/edit
> h4. Q1. What are you trying to do?
> Let a Spark job on Kubernetes run executors of different sizes as long as 
> every executor keeps the same memory per CPU core. The user states a ladder 
> of executor sizes ({{spark.kubernetes.executor.shape.ladder=16,8,4}}) instead 
> of one fixed size; heap per core is derived from spark.executor.memory / 
> spark.executor.cores. The driver requests the largest shape first; when 
> Kubernetes reports the pod Unschedulable past a timeout, the driver replaces 
> it with pods one rung down that sum to the same slot count, and periodically 
> probes upward again. Nothing visible to the task scheduler changes: 
> {{spark.task.cpus}}, memory per core and custom resources per task stay 
> uniform; only cores per executor and total memory per executor float. 
> Everything outside the Kubernetes allocator keeps counting executors in 
> reference-shape units, so {{spark.dynamicAllocation.*}} and 
> {{SparkContext.requestTotalExecutors}} keep their meaning.
> h4. Q2. What problem is this NOT designed to solve?
> * Different memory-per-core ratios inside one ResourceProfile (use two 
> profiles).
> * Growing or shrinking a running executor (heap and registered cores are 
> fixed at start).
> * Choosing node types, zones or spot vs on-demand. That stays with the 
> cluster (GKE compute classes, Karpenter NodePools, cluster-autoscaler).
> * Reading node capacity from the driver (optional later strategy, needs node 
> RBAC).
> * YARN / Standalone. Core model is cluster-manager neutral; only the 
> Kubernetes {{direct}} pods allocator is implemented. StatefulSet and 
> Deployment allocators reject the config.
> h4. Q3. How is it done today, and what are the limits?
> Homogeneity is an accident of the allocation path, not a scheduler 
> requirement. {{ExecutorAllocationManager}} targets an executor count per 
> profile; {{ExecutorPodsAllocator}} reconciles counts per rpId and never looks 
> at pod size or a Pending pod's {{PodScheduled=False}} condition; 
> {{BasicExecutorFeatureStep}} derives pod shape purely from the profile. 
> {{RegisterExecutor}} already carries per-executor cores, so the task 
> scheduler tolerates heterogeneous cores today. 
> {{ResourceProfileManager.canBeScheduled}} requires exact rpId match, so 
> sibling profiles per size would not work. Users work around it with small 
> homogeneous executors and pay per-JVM overhead (384 MiB overhead floor, 
> smaller unified memory pools, N x M shuffle connections, broadcast per JVM, 
> slower spot decommission).
> h4. Q4. What is new and why will it succeed?
> * The invariant is memory per core, not pod size, so this is an allocator and 
> profile change, not a scheduler change.
> * One ResourceProfile carries a ladder of shapes under one rpId; 
> {{canBeScheduled}} is unchanged. Phase 1 is configuration-only: no change to 
> the ResourceProfile API or the scheduler backend contract; the Kubernetes 
> backend converts the executor-count target to cores itself.
> * Kubernetes tells the driver what fits through the pod watch Spark already 
> has (new {{PodUnschedulable}} state); no new RBAC.
> * On clusters with compute classes the two decisions compose: the platform 
> picks node type and capacity type, Spark picks pod size. Node-aware packing 
> in the driver (approach B) would duplicate the platform's job, needs 
> cluster-scoped node RBAC and fights consolidation.
> h4. Q5. Who cares?
> Platform teams running Spark on mixed or spot fleets (higher node fill, 
> higher spot obtainability), job owners (one ratio replaces per-cluster size 
> tuning, per-task memory stays comparable), and operators of autoscaled 
> clusters (the feature cooperates with the autoscaler instead of bypassing it).
> h4. Q6. Risks
> Downshift fires only on an insufficient cpu/memory scheduler message, not on 
> taint, affinity or PVC causes; the upshift probe backs off exponentially and 
> carries a low PriorityClass so it never triggers an autoscaler scale-up. 
> Downshift timeout vs autoscaler provisioning latency (mitigated by reading 
> {{TriggeredScaleUp}} / {{NotTriggerScaleUp}} / {{Nominated}} pod events); pod 
> churn on downshift (bounded by {{maxPendingPods}}, reuses the excess-pending 
> deletion path); executor-side conf readers seeing app-level 
> {{spark.executor.memory}} / {{cores}} (executor backend writes pod values 
> into its local SparkConf); overhead floor squeezing small shapes (per-shape 
> split and warning); idle removal under-shooting the slot target (subtract 
> real slots); UI and history server (additive event fields).
> h4. Q7. How long will it take?
> About two months for Phase 1 across four PRs, all inside the Kubernetes 
> module plus one backend method: ladder allocation and per-rung sizing 
> (prototyped); scheduler backend accounting (registered-cores gate, kill-path 
> weights, executor cores from the launch argument, metrics); upshift probe and 
> autoscaler events; docs. Prerequisites SPARK-61267 and SPARK-61268 are 
> already open. The ResourceProfile API for ladders on custom profiles is Phase 
> 2 and not part of this vote.
> h4. Q8. Exams
> Mid-term: a single-rung ladder behaves byte-for-byte like today in 
> {{TaskSchedulerImplSuite}} and {{ExecutorAllocationManagerSuite}}; executors 
> report pod values in SparkEnv.conf and the Executors tab. Final: 
> {{ExecutorPodsAllocatorSuite}} downshift and upshift scenarios; k8s 
> integration test on a two-node-size cluster where total slots match the 
> target and every executor has memory / cores equal to the ratio; StatefulSet 
> and Deployment allocators fail fast.
> Design baseline: apache/spark master, commit 4686647b3a1 (2026-11-09).



--
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