Dreamstick9 opened a new pull request, #57748:
URL: https://github.com/apache/spark/pull/57748

   ### What changes were proposed in this pull request?
   
   SPARK-36206 added shuffle corruption diagnosis based on shuffle checksums, 
but only for shuffle
   blocks that are not merged. When a push-merged shuffle chunk is detected as 
corrupt, the reducer
   falls back to the original shuffle blocks and reports nothing about the 
cause, because no checksum
   of the merged data exists. SPARK-37695 made that explicit by skipping the 
diagnosis of a
   `ShuffleBlockChunkId` with a `TODO SPARK-36284`. This PR fills that gap.
   
   Server side, `RemoteBlockPushResolver` now calculates a checksum per chunk 
of a merged shuffle
   partition while it merges the pushed blocks, and stores them next to the 
merged data, index and
   meta files as 
`shuffleMerged_<appId>_<shuffleId>_<shuffleMergeId>_<reduceId>.checksum.<ALGORITHM>`
   (one long per chunk, so one entry less than the index file, which has a 
leading zero offset). The
   checksum algorithm is part of the file name, as it is for the checksum file 
of a non-merged shuffle
   block, so the checksums can never be compared against those of a different 
algorithm after the
   shuffle service is reconfigured.
   
   The running checksum of the current chunk is fed in `writeBuf`, right after 
the data of a block has
   been written, so merging does not read the merged data back in the common 
case. Two situations make
   the running checksum stop describing the current chunk, and both are 
detected by tracking the
   position up to which the checksum has consumed the data file:
   
   - a block that was partially written is abandoned, and its data is 
overwritten by the next block,
     since `dataFilePos` only advances when a block commits;
   - a block is still being written when the shuffle merge is finalized, and 
its data is truncated
     away.
   
   In both cases the checksum of the chunk is recalculated from the merged data 
file when the chunk
   gets sealed. The checksum entry is written from `updateChunkInfo`, together 
with the chunk offset
   and the chunk bitmap, and it follows the same tracked-position and 
rewind-on-retry protocol, so the
   checksum file stays aligned with the index file even when the update of the 
index or meta file
   fails and is retried.
   
   Client side, a new `DiagnoseShuffleChunkCorruption` message asks the shuffle 
service which merged a
   chunk for the cause of its corruption, and the response reuses 
`CorruptionCause`.
   `ShuffleBlockFetcherIterator` now diagnoses a corrupt shuffle chunk before 
it falls back to the
   original shuffle blocks. The diagnosis of a merged chunk is answered by 
comparing the checksum the
   reducer calculated against the stored one and against a recalculation of the 
chunk on disk, exactly
   like `ExternalShuffleBlockResolver.diagnoseShuffleBlockCorruption` does for 
a non-merged block, so
   `DISK_ISSUE`, `NETWORK_ISSUE` and `CHECKSUM_VERIFY_PASS` keep their existing 
meanings. A chunk that
   was merged with a different algorithm than the reducer uses is reported as
   `UNSUPPORTED_CHECKSUM_ALGORITHM`.
   
   Two shuffle service configurations are added:
   
   | Configuration | Default |
   |---|---|
   | `spark.shuffle.push.server.mergedShuffleChecksum.enabled` | `true` |
   | `spark.shuffle.push.server.mergedShuffleChecksum.algorithm` | `ADLER32` |
   
   The default algorithm matches the default of 
`spark.shuffle.checksum.algorithm`, so the two line up
   out of the box. An unsupported algorithm only disables the calculation with 
a warning instead of
   failing the merge.
   
   The checksums are strictly diagnostic, so nothing about them is allowed to 
affect the merge. A
   failure to write or recompute them gives up on the checksums of that shuffle 
partition, logs a
   warning and deletes the checksum file when the partition is finalized, 
instead of counting towards
   `spark.shuffle.push.server.ioExceptionsThresholdDuringMerge` or failing the 
finalization of an
   otherwise correctly merged partition. A chunk of such a partition then 
simply diagnoses as
   `UNKNOWN_ISSUE`.
   
   Notes for reviewers, mostly on the trade-offs I would like your opinion on:
   
   1. The checksum is calculated over the data as the shuffle service received 
it, so it covers
      corruption of the merged data on the shuffle service disk and in transit 
to the reducer. It
      cannot distinguish corruption that already happened while a mapper pushed 
the block, which is
      noted in the code.
   2. The diagnosis of a merged chunk is informational. A corrupt chunk still 
falls back to the
      original shuffle blocks exactly as before, since a chunk is never 
re-fetched. It does however
      delay that fallback by the drain of the chunk and one synchronous RPC, 
and the RPC uses
      `spark.network.timeout` like the existing diagnosis of a shuffle block 
does. The connection to
      the shuffle service is normally still cached from the fetch of the chunk 
itself, but a shuffle
      service that dies between the fetch and the diagnosis delays a fallback 
that is immediate
      today. Happy to gate this on a separate configuration if you prefer.
   3. The default of `spark.shuffle.push.server.mergedShuffleChecksum.enabled` 
is `true`, matching
      `spark.shuffle.checksum.enabled`. It costs one checksum pass over the 
merged data, one extra
      file, and one more file descriptor per actively merging partition, which 
is held for as long as
      the index and meta file descriptors are. The checksum file is written 
exactly as often as the
      index file, once per sealed chunk, so it follows the same 
`MergeShuffleFile` pattern, but it
      could be opened only when a chunk is sealed if the fourth descriptor is a 
concern. Please say
      so if you would rather have the whole thing off by default.
   4. A chunk that was merged with a different algorithm than the reducer uses 
is reported as
      `UNSUPPORTED_CHECKSUM_ALGORITHM` even though both algorithms are 
supported on their own, since
      appending a new `Cause` would break a reducer running an older Spark 
version. The shuffle
      service logs which two algorithms disagree.
   5. `MergedShuffleFileManager` gains a default method that returns 
`UNKNOWN_ISSUE`, so third party
      implementations and `NoOpMergedShuffleFileManager` keep working unchanged.
   
   ### Why are the changes needed?
   
   Push-based shuffle is the only place where Spark detects shuffle data 
corruption and cannot say
   anything about its cause. When a merged chunk is corrupt, the reducer 
silently falls back to the
   original blocks, so a shuffle service with a failing disk keeps corrupting 
merged data and the only
   symptom is degraded performance from repeated fallbacks. With the merged 
chunks checksummed, the
   same diagnosis that is available for regular shuffle blocks since Spark 3.2 
tells the user whether
   the corruption came from the shuffle service disk or from the network.
   
   ### Does this PR introduce _any_ user-facing change?
   
   Yes. Two new shuffle service configurations, documented in 
`docs/configuration.md`. With the
   default configuration a shuffle service with push-based shuffle enabled 
writes one additional
   checksum file per merged shuffle partition, and the reducer logs the 
diagnosed cause of a corrupt
   merged shuffle chunk, for example:
   
   ```
   BlockChunk shuffleChunk_0_0_2_1 is corrupted due to DISK_ISSUE
   ```
   
   Previously the corruption of a merged chunk was reported as:
   
   ```
   BlockChunk shuffleChunk_0_0_2_1 is corrupted but corruption diagnosis is 
skipped due to lack of
   shuffle checksum support for push-based shuffle.
   ```
   
   ### How was this patch tested?
   
   New unit tests, plus existing ones extended:
   
   - `RemoteBlockPushResolverSuite`: the shared `validateChunks` helper now 
also verifies that every
     stored checksum matches the data its chunk ended up with in the merged 
shuffle data file, so all
     the existing merge tests cover the checksums, including the ones that 
inject `IOException`s into
     the index and meta file updates. New tests cover the checksums of the 
merged chunks and the entry
     count against the index file, a chunk whose data is overwritten after a 
block push failed, a
     chunk sealed while a block push is still in flight at finalization, the 
four diagnosis outcomes
     (`CHECKSUM_VERIFY_PASS`, `NETWORK_ISSUE`, `DISK_ISSUE` against a data file 
corrupted after the
     merge, and `UNSUPPORTED_CHECKSUM_ALGORITHM`), diagnosis of an unknown 
chunk, diagnosis when the
     checksum calculation is disabled, and a checksum file that cannot be 
written, which must leave
     the merge and its `MergeStatuses` untouched. Cleanup assertions for the 
checksum file were added
     to the outdated shuffle merge id and `removeShuffleMerge` tests.
   - `ExternalBlockHandlerSuite`: the new RPC is routed to the 
`MergedShuffleFileManager` and answered
     with a `CorruptionCause`.
   - `BlockTransferMessagesSuite`: round trip of 
`DiagnoseShuffleChunkCorruption`, and of
     `DiagnoseCorruption` and `CorruptionCause`, which were not covered before.
   - `ShuffleBlockFetcherIteratorSuite`: the diagnosis of a 
`ShuffleBlockChunkId` reaches the shuffle
     service which merged the chunk, using the external shuffle service port 
for a push-merged-local
     chunk, and a corrupt merged chunk is diagnosed before it falls back to the 
original blocks.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to