Skip to content

[SPARK-58107][CORE][FOLLOWUP] Keep the local-checkpoint sealed checksum as a tombstone past last-replica loss#57499

Closed
juliuszsompolski wants to merge 1 commit into
apache:masterfrom
juliuszsompolski:julek_data/checksum-seal-verify-tombstone
Closed

[SPARK-58107][CORE][FOLLOWUP] Keep the local-checkpoint sealed checksum as a tombstone past last-replica loss#57499
juliuszsompolski wants to merge 1 commit into
apache:masterfrom
juliuszsompolski:julek_data/checksum-seal-verify-tombstone

Conversation

@juliuszsompolski

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Follow-up to SPARK-58107 (detect and repair
non-deterministic local checkpoints via RDD-block content checksums). Makes the master's per-block
sealed checksum (sealedChecksums in BlockManagerMasterEndpoint) a tombstone that survives the
loss of the block's last replica and is reclaimed only when the RDD is removed.

Previously sealedChecksums(blockId) was dropped alongside blockLocations when a block's last
replica went away (the empty-locations branches of updateBlockInfo and removeBlockManager).

  • updateBlockInfo / removeBlockManager: on empty locations, keep sealedChecksums (still drop
    blockLocations / blockChecksums). A late replica matching the seal is admitted, a divergent one
    rejected by checksumSealRejectsUpdate.
  • removeRdd: reclaim the RDD's tombstones via a new sealedBlocksByRdd index (rddId -> its sealed
    block ids, populated in sealRddChecksums), so reclaim is O(the RDD's sealed blocks) rather than a
    scan of the whole sealedChecksums map on the single-threaded endpoint.
  • Only sealedChecksums (a Long per sealed partition, plus its RDDBlockId in the index) is
    tombstoned; blockChecksums is still dropped at empty-locations.

Why are the changes needed?

After a checkpoint is sealed and its lineage is cut, losing the winning replica erased the seal. A
late divergent copy of that block -- e.g. from a zombie task of a superseded stage attempt that was
still running from before the seal -- then had no seal to reject it, so updateBlockInfo admitted it
and later readers could silently observe a different version than earlier readers. Losing every
replica of a finalized checkpoint must mean data loss, not a silent version switch. The tombstone
preserves the seal so the divergent late copy is still rejected; retention is bounded by RDD lifetime
(reclaimed in removeRdd).

Does this PR introduce any user-facing change?

No. spark.checkpoint.local.verifyChecksum.enabled gates the seal path; behavior only differs when it
is enabled, and only in the (previously silently wrong) last-replica-loss + divergent-late-copy case.

How was this patch tested?

New BlockManagerSuite tests drive the master RPC sequence the race produces (no scheduler needed):
after a sealed block loses its last replica -- via an invalid-level updateBlockInfo and via
removeExecutor -- the seal survives as a tombstone, a divergent late registration is rejected and a
matching one admitted; and removeRdd reclaims a zero-replica tombstone. The existing "sealed
checksums are cleared when the RDD is removed" test still passes.

Was this patch authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Opus 4.8)

…um as a tombstone past last-replica loss

A sealed local-checkpoint block's checksum was dropped from the master when the block's last replica
went away (empty-locations branches of updateBlockInfo / removeBlockManager). After a checkpoint is
sealed and its lineage cut, losing the winning replica then erased the seal, so a late divergent copy
of that block (e.g. a zombie task from a superseded stage attempt) had nothing to reject it in
checksumSealRejectsUpdate and later readers could silently observe a different version.

Keep sealedChecksums as a tombstone: it survives the loss of the last replica (a matching late copy is
admitted, a divergent one rejected) and is reclaimed only with the RDD in removeRdd, via a new
sealedBlocksByRdd index so the reclaim is O(the RDD's sealed blocks) rather than a full-map scan.
Only sealedChecksums is tombstoned; blockChecksums is still dropped eagerly.
@juliuszsompolski

Copy link
Copy Markdown
Contributor Author

cc @cloud-fan

@cloud-fan cloud-fan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 blocking, 0 non-blocking, 0 nits.
The metadata lifetime, rejection path, and RDD-scoped cleanup are internally consistent, with focused regression coverage for the changed behavior.

Verification

Traced sealRddChecksums through both replica-removal paths and removeRdd, confirmed the reverse index is populated only alongside a sealed checksum and drained at RDD removal, and independently checked the new tests against those production paths. The text and contract scanners examined all prepared candidates and reported no findings.

@cloud-fan cloud-fan closed this in b65ef3e Jul 24, 2026
cloud-fan pushed a commit that referenced this pull request Jul 24, 2026
…um as a tombstone past last-replica loss

### What changes were proposed in this pull request?

Follow-up to [SPARK-58107](https://issues.apache.org/jira/browse/SPARK-58107) (detect and repair
non-deterministic local checkpoints via RDD-block content checksums). Makes the master's per-block
sealed checksum (`sealedChecksums` in `BlockManagerMasterEndpoint`) a tombstone that survives the
loss of the block's last replica and is reclaimed only when the RDD is removed.

Previously `sealedChecksums(blockId)` was dropped alongside `blockLocations` when a block's last
replica went away (the empty-locations branches of `updateBlockInfo` and `removeBlockManager`).

- `updateBlockInfo` / `removeBlockManager`: on empty locations, keep `sealedChecksums` (still drop
  `blockLocations` / `blockChecksums`). A late replica matching the seal is admitted, a divergent one
  rejected by `checksumSealRejectsUpdate`.
- `removeRdd`: reclaim the RDD's tombstones via a new `sealedBlocksByRdd` index (rddId -> its sealed
  block ids, populated in `sealRddChecksums`), so reclaim is O(the RDD's sealed blocks) rather than a
  scan of the whole `sealedChecksums` map on the single-threaded endpoint.
- Only `sealedChecksums` (a `Long` per sealed partition, plus its `RDDBlockId` in the index) is
  tombstoned; `blockChecksums` is still dropped at empty-locations.

### Why are the changes needed?

After a checkpoint is sealed and its lineage is cut, losing the winning replica erased the seal. A
late divergent copy of that block -- e.g. from a zombie task of a superseded stage attempt that was
still running from before the seal -- then had no seal to reject it, so `updateBlockInfo` admitted it
and later readers could silently observe a different version than earlier readers. Losing every
replica of a finalized checkpoint must mean data loss, not a silent version switch. The tombstone
preserves the seal so the divergent late copy is still rejected; retention is bounded by RDD lifetime
(reclaimed in `removeRdd`).

### Does this PR introduce _any_ user-facing change?

No. `spark.checkpoint.local.verifyChecksum.enabled` gates the seal path; behavior only differs when it
is enabled, and only in the (previously silently wrong) last-replica-loss + divergent-late-copy case.

### How was this patch tested?

New `BlockManagerSuite` tests drive the master RPC sequence the race produces (no scheduler needed):
after a sealed block loses its last replica -- via an invalid-level `updateBlockInfo` and via
`removeExecutor` -- the seal survives as a tombstone, a divergent late registration is rejected and a
matching one admitted; and `removeRdd` reclaims a zero-replica tombstone. The existing "sealed
checksums are cleared when the RDD is removed" test still passes.

### Was this patch authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Opus 4.8)

Closes #57499 from juliuszsompolski/julek_data/checksum-seal-verify-tombstone.

Authored-by: Juliusz Sompolski <julek@databricks.com>
Signed-off-by: Wenchen Fan <wenchen@databricks.com>
(cherry picked from commit b65ef3e)
Signed-off-by: Wenchen Fan <wenchen@databricks.com>
@cloud-fan

Copy link
Copy Markdown
Contributor

Merge Summary:

Posted by merge_spark_pr.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants