[SPARK-43086][CORE] Add configurable bin-packing task placement#57484
Open
starcatmeow wants to merge 1 commit into
Open
[SPARK-43086][CORE] Add configurable bin-packing task placement#57484starcatmeow wants to merge 1 commit into
starcatmeow wants to merge 1 commit into
Conversation
Introduce spark.scheduler.taskPlacement.strategy with SPREAD as the default and BIN_PACK as an opt-in strategy. Preserve the PROCESS_LOCAL, NODE_LOCAL, and RACK_LOCAL scheduling passes, then pack executor offers during the NO_PREF and ANY passes by preferring executors with running or current-round assigned tasks before using lexicographic executor ID order. Add coverage for configuration validation, spread and bin-pack placement, locality, multiple TaskSets, custom resources, barrier tasks, and provisional barrier assignments.
starcatmeow
force-pushed
the
rayli/spark-bin-pack-task-placement-oss
branch
from
July 24, 2026 04:13
e2c2dd8 to
56b0c6a
Compare
Member
viirya
approved these changes
Jul 24, 2026
viirya
left a comment
Member
There was a problem hiding this comment.
I traced the scheduler paths independently and this holds up — including a few spots worth spelling out since it's a placement change.
- SPREAD is behavior-preserving. With
useBinPacking = falsethe new innerdo { ... } while (useBinPacking && launchedTask)runs the body exactly once per offer index, soresourceOfferSingleTaskSetstill does a single pass launching at most one task per offer, and the outerdo/while(launchedTaskAtCurrentMaxLocality)still drives the round-robin. Default behavior is unchanged. - The busy/idle predicate is well chosen.
isExecutorBusy(execId) || availableCpus(i) < shuffledOffers(i).cores— the first term catches executors made busy by prior rounds or earlieraddRunningTasks in this round, and the second catches provisional barrier assignments in the current round (which decrementavailableCpusbut aren't registered as running until the whole barrier set launches). The comment captures this well, and the "treats barrier locality assignments as busy" test pins it. - Shared index space stays intact. Only the iteration order is reordered via
offerIndicesByExecutorId;shuffledOffers/availableCpus/availableResources/tasksare all still addressed by the original indexi, so there's no re-indexing hazard, and the sort happens once perresourceOfferscall. - Delay-scheduling accounting is unaffected.
hasScheduleDelayRejectonly fires atmaxLocality == ANYwith pending-but-unschedulable tasks. BIN_PACK just moves the repeated probing of a single offer from the outer loop into the inner loop; the final no-launch probe is what can set the reject in both cases, sonoDelayScheduleRejectsreaches the same result. - Locality is preserved — BIN_PACK only applies at NO_PREF/ANY, and the PROCESS_LOCAL/NODE_LOCAL tests confirm it.
Two non-blocking notes:
- Executor IDs are sorted lexicographically (
sortBy(_.executorId)on the string), so e.g."10"sorts before"2". That's fine for correctness — bin-packing only needs a stable order — and the doc does say "lexicographic executor ID order," so it's deliberate. Just flagging that since executor IDs are numeric-looking, the ordering can read as surprising; a half-sentence noting it's lexicographic purely for determinism would help the next reader. - Under BIN_PACK the inner loop already fills each offer to exhaustion, so the enclosing
do/while(launchedTaskAtCurrentMaxLocality)will make one extra scan that launches nothing before exiting. Harmless, just a minor redundant pass — not worth changing.
Docs and test coverage are thorough. LGTM.
Member
|
this one looks very similar to #56957 |
Contributor
|
So the motivation seems to make auto-scale more efficient? Can we have a proper design discussion for the end to end auto-scale workflow? Why changing the scheduler is the best design choice? Just for example, one idea can be: when the auto scaler predicate an upcoming idle time window, it can pick some executors and mark them as decommissioned, then scheduler will not assign new tasks to these executors, and auto scaler can kill them after current tasks are done. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes were proposed in this pull request?
This PR adds an opt-in bin-packing task placement strategy to
TaskSchedulerImpl.It introduces
spark.scheduler.taskPlacement.strategywith two values:SPREAD, the default, preserves the existing behavior of cycling through shuffledexecutor offers one task at a time.
BIN_PACKfills an eligible executor before moving to the next one.BIN_PACKapplies only during theNO_PREFandANYscheduling passes.PROCESS_LOCAL,NODE_LOCAL, andRACK_LOCALretain Spark's existing shuffled offerorder, so locality continues to take precedence over packing.
For bin-packed passes, executors with running tasks or tasks assigned earlier in the
current
resourceOfferscall are considered before idle executors. Each group is orderedlexicographically by executor ID. The scheduler sorts offer indices once per
resourceOfferscall, leavingshuffledOffers,availableCpus,availableResources,and the output task buffers in their existing shared index space. It then performs a
stable busy/idle partition for each TaskSet and bin-packed pass so that later TaskSets
observe assignments made earlier in the scheduling round.
The PR also documents the new configuration and adds tests for the configuration,
placement order, locality, multiple TaskSets, custom resources, and barrier tasks.
Why are the changes needed?
SPARK-43086 describes a resource
efficiency issue when dynamic allocation is used with the existing spread placement
behavior.
Dynamic allocation removes an executor only after it becomes idle. When a stage has
fewer tasks than the available cluster slots, spreading those tasks across executors can
keep more executors busy than necessary and delay scale-down.
For example, if four executors have two task slots each, a four-task stage can place one
task on each of four executors with
SPREAD. WithBIN_PACK, it can instead place twotasks on each of two executors, allowing the other two executors to become idle and be
removed.
The new strategy is opt-in to avoid changing Spark's existing default placement and
latency behavior.
Does this PR introduce any user-facing change?
Yes. It adds the following configuration:
spark.scheduler.taskPlacement.strategy=BIN_PACKSupported values are:
SPREAD: preserves the existing placement behavior and remains the default.BIN_PACK: fills busy executors first and then idle executors, using lexicographicexecutor ID order within each group, during the
NO_PREFandANYpasses.Applications that do not set this configuration retain the existing behavior.
Locality-specific placement for
PROCESS_LOCAL,NODE_LOCAL, andRACK_LOCALisunchanged.
How was this patch tested?
The scheduler unit suite passed:
build/sbt 'core/testOnly org.apache.spark.scheduler.TaskSchedulerImplSuite'All 122 tests passed. The added coverage includes:
SPREADbehavior;BIN_PACKexecutor ordering;PROCESS_LOCALandNODE_LOCALplacement;ANYlocality;Scalastyle and a full package build also passed:
The two strategies were additionally tested with real executor JVMs using:
local-cluster[4,2,1024];NO_PREFtasks;The steady executor counts observed during each stage were:
SPREADBIN_PACKThe Spark Event Timeline showed that
SPREADfirst removed executors during thetwo-task stage, while
BIN_PACKremoved two executors during the four-task stage andanother executor during the two-task stage.
SPREAD:BIN_PACK:Was this patch authored or co-authored using generative AI tooling?
Generated-by: OpenAI Codex (GPT-5)