[SPARK-58004][SQL][FOLLOWUP] Preserve AQE fan-in bound through local shuffle reads#57128
[SPARK-58004][SQL][FOLLOWUP] Preserve AQE fan-in bound through local shuffle reads#57128sunchao wants to merge 2 commits into
Conversation
|
cc @dongjoon-hyun @viirya a follow-up of #57087 |
There was a problem hiding this comment.
This is not a regression of SPARK-58004 itself because the fan-in bound is correctly preserved. However, the check looks too stricter than necessary, so users who set the config lose the local shuffle read optimization almost entirely.
The two spec types are not equivalent per unit of reducer span:
A CoalescedPartitionSpec spanning k reducers fetches k × numMappers blocks over the network — this is what SPARK-58004 bounds.
A PartialMapperPartitionSpec spanning k reducers reads only k blocks from a single mapper, typically locally.
Yet the PR compares both spans against the same limit. Since a bound-constrained coalesced read has spans ≈ max, the equivalent local-read spec spans ≈ max × numMappers, which always fails the check when numMappers ≥ 2. So the rewrite is reverted even when the local-read task would read the same number of blocks as the retained coalesced task — with less network traffic.
In short, setting the config effectively seems to disable local shuffle reads on those stages, forfeiting their benefit in cases where the fan-in pressure the config guards against would not actually recur.
Maybe, compare PartialMapperPartitionSpec spans against max × numMappers (equivalent per-task block count), or regenerate specs with higher parallelism instead of reverting.
|
Thanks @dongjoon-hyun for the detailed analysis. I agree with the concern, and I think it can be pushed one step further, which changes what the right fix looks like. The local read rewrite is per-task block-count preserving by construction. It reuses the coalesced read's parallelism A concrete example with
The rejected plan is strictly better, which is why the check as written effectively disables local reads whenever the bound was binding. This also means comparing
My preference is the first option, since I could not find a cost in the local read path that scales with the literal span rather than the block count — but either way the current all-or-nothing revert seems to be the least favorable choice for users who set this config. |
What changes were proposed in this pull request?
This is a follow-up to #57087.
OptimizeShuffleWithLocalReadnow checks the mapper-oriented partition specs it proposes againstspark.sql.adaptive.coalescePartitions.maxReducerPartitionsPerTask. If anyPartialMapperPartitionSpecspans too many reducer partitions, or anyCoalescedMapperPartitionSpecreads too many reducers, the rule preserves the existing bounded shuffle read. Safe local-read rewrites are unchanged.The patch also adds an AQE regression test that exercises both mapper-oriented local-read spec forms and verifies that the bounded coalesced reads remain in the final plan.
Why are the changes needed?
SPARK-58004 added a hard limit on the number of reducer partitions that AQE can combine into one task. However, the local shuffle reader runs after shuffle partition coalescing and can replace the bounded
CoalescedPartitionSpecvalues with mapper-oriented specs whose reducer spans exceed that limit.Without this follow-up, enabling local shuffle reads can therefore bypass the new fan-in bound and recreate the excessive per-task fetch pressure that #57087 is intended to prevent.
Does this PR introduce any user-facing change?
Yes, compared with the current unreleased
masterbranch. When a proposed local shuffle read would exceedspark.sql.adaptive.coalescePartitions.maxReducerPartitionsPerTask, Spark now keeps the existing coalesced shuffle read instead. Released Spark versions are not affected.How was this patch tested?
Added and ran the focused regression test:
Ran the adjacent positive-path local shuffle reader test:
Ran the coalescing-disabled finite-cap regression:
Ran style checks:
Was this patch authored or co-authored using generative AI tooling?
Generated-by: OpenAI Codex (GPT-5)