[SPARK-58830][CORE] Add MapStatus checksum to detect metadata corruption between mapper and reducer - #58062
Open
wang-haihua wants to merge 1 commit into
Open
[SPARK-58830][CORE] Add MapStatus checksum to detect metadata corruption between mapper and reducer#58062wang-haihua wants to merge 1 commit into
wang-haihua wants to merge 1 commit into
Conversation
wang-haihua
force-pushed
the
worktree-mapstatus-checksum
branch
4 times, most recently
from
September 15, 2026 06:15
968f2e3 to
d5af4f5
Compare
…ion between mapper and reducer
wang-haihua
force-pushed
the
worktree-mapstatus-checksum
branch
from
September 15, 2026 08:28
d5af4f5 to
c9a7fdf
Compare
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 integrity checksum to
MapStatusthat covers the set of non-emptypartition indices, and verifies it wherever a
MapStatusis consumed to plan shuffleblock fetches.
MapStatusChecksumutility computes a checksum (CRC32 or Adler32) over theindices of non-empty partitions in a
partitionLengthsarray. It returnsNonewhenthere are no non-empty partitions.
MapStatus(bothCompressedMapStatusandHighlyCompressedMapStatus) gains a newnonEmptyChecksum: Option[Int]field, computed at construction time when enabled, andcarried through Java (
Externalizable) serialization via a presence flag followed bythe value, matching the existing pattern used for other optional fields.
MapOutputTracker.verifyChecksumOrFailrecomputes the checksum from theMapStatusactually consumed by a reducer and compares it against the stored value. A mismatch
throws a
FetchFailedException, reusing Spark's existing fetch-failure / stage-retryrecovery path instead of introducing a new failure mode.
spark.shuffle.mapStatus.checksum.enabled(defaultfalse)spark.shuffle.mapStatus.checksum.algorithm(defaultCRC32, also supportsADLER32)The checksum is computed over the non-empty/empty boundary of each partition rather
than the sizes themselves, because sizes are already lossily compressed/estimated by
CompressedMapStatus/HighlyCompressedMapStatusand can legitimately differ inrepresentation while still being correct. Only the non-empty/empty boundary matters for
the silent-data-loss failure mode this PR targets.
Why are the changes needed?
MapStatusis the most frequently accessed piece of shuffle metadata: every mapperproduces one, the driver holds one per map task, and every reducer consults it to decide
which blocks to fetch. If a
MapStatus'spartitionLengths(or its compressedrepresentation) is corrupted in memory or in transit between the mapper and a reducer --
e.g. due to a transient hardware fault, a JVM/memory corruption, or a serialization bug
-- a partition that actually has data on disk can appear to have size 0. Because block
fetchers are intentionally designed to skip zero-size blocks
(
MapStatus.getSizeForBlock(reduceId) == 0), the reducer silently skips fetching thatblock. The on-disk data is never read and is effectively lost, with no exception, no
failed stage, and no log signal anywhere in the pipeline.
We hit this in production: a job produced fewer output rows than an otherwise-equivalent
rerun, with no errors in the driver or executor logs. Comparing Spark UI SQL metrics for
the two runs showed shuffle "records written" and "records read" diverging by a large
margin at an Exchange node in the faulty run. Instrumenting
MapStatusat several pointsalong its mapper -> driver -> reducer path showed a handful of non-empty partition
entries had been flipped to zero somewhere along that path, while the corresponding
shuffle data files on disk were complete and correct. The root cause traced back to a
hardware fault on one worker node that silently corrupted the
MapStatusobject held inthe driver's memory. Full writeup: SPARK-58830.
Existing safeguards don't catch this class of failure:
shuffle bytes is detectable when a reducer actually reads them. But when the corruption
is in the metadata (a non-empty partition reported as empty), the reducer never
attempts to read that block, so the data checksum path is never exercised.
is empty because it was merged by a bad node) via a fallback to fetching the original
blocks. It doesn't cover non-empty-to-empty corruption of a mapper's own
MapStatusoutside push-based merging.
speculation/non-deterministic shuffle keys, where only one attempt's
MapStatusshould survive. It doesn't address transport/memory corruption of a single
MapStatus.None of the above detects the case where a
MapStatusthat was correct when the mapperproduced it gets corrupted before a reducer consumes it. This PR closes that gap.
Does this PR introduce any user-facing change?
No behavior change by default: both new configs are internal and
spark.shuffle.mapStatus.checksum.enableddefaults tofalse.When explicitly enabled, a
MapStatusmetadata checksum mismatch now surfaces as aFetchFailedException, which triggers Spark's standard stage-retry recovery, instead of silently under-reading shuffle data.How was this patch tested?
MapStatusChecksumSuitecover:compute/recomputereturningNonefor all-empty inputs, invariance to size values (only non-empty indices matter), sensitivity to a single-bit change in the non-empty set, agreement with an independent CRC32 reference implementation, ADLER32 vs CRC32 producing different values, rejection of unsupported algorithm names,recomputeagreeing withcomputeand detecting a changed non-empty set, and Java serialization round-trips of the new field on bothCompressedMapStatusandHighlyCompressedMapStatus.MapOutputTrackerSuite,"MapStatus checksum mismatch triggers FetchFailedException", exercisesverifyChecksumOrFailagainst a realMapOutputTrackerMasterwith a corrupted checksum and asserts aFetchFailedExceptionis thrown.build/sbt 'core/testOnly *MapStatusChecksumSuite'andbuild/sbt 'core/testOnly *MapOutputTrackerSuite'-- all tests pass.Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Claude Sonnet 5)