fix: deferred copy tail double-counted in completed object plaintext size#126
Merged
ServerSideHannes merged 2 commits intoJul 10, 2026
Merged
Conversation
…size A >copy-cutoff UploadPartCopy defers its sub-5MB tail on part 1 and folds it into part 2's streaming copy. Part 1's PartMetadata also counted the tail, so CompleteMultipartUpload stored total_plaintext_size one tail too large and HEAD Content-Length no longer matched the source: rclone failed every such copy with 'corrupted on transfer: sizes differ' (398 files, +1,129,664 bytes each, backup run sm_20260709080013UTC). Part metadata now reflects stored bytes only; a tail flushed at complete time bumps the receiving part's plaintext_size alongside ciphertext_size. Complete logs PART_PLAINTEXT_MISMATCH when a client part's plaintext disagrees with its internal parts.
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.
Problem
Overnight (10 Jul, 00:22–01:53 UTC) the Scylla backup run
sm_20260709080013UTCfailed 398 versioning moves (~1.14 TiB) on the new node (rack-12) with rclone reporting:The destination was exactly +1,129,664 bytes on every file regardless of tail size — the size of the deferred hybrid tail for a part pinned at rclone's copy-cutoff (4,999,341,932 bytes) against prod's 50MB/6 internal frame layout.
Root cause
For an object larger than rclone's
copy_cutoff(4.656 GiB), rclone issues a 2-part UploadPartCopy. Since #124:plaintext_size = segments + tailfor bytes it did not store.take_deferred_copy_tailand prepends it asleading_plaintext— correctly counting it, since the tail bytes physically live in part 2.total_plaintext_size= object + tail → HEADContent-Lengthinflated → rclone's post-copy size check fails and it refuses to finish the move. Data on the wire was correct and in order; only the accounting was wrong.Smaller objects (single-range copies, e.g. all
peopleSSTables) never split, which is why only the ~6.8 GBcompaniesobjects failed.Fix
_passthrough_copy_part: part 1's metadata reflects stored bytes only (segments; no deferred tail)._flush_deferred_copy_tail_for_complete: when the tail is flushed at complete (no later part consumed it), bump the receiving part'splaintext_sizealongsideciphertext_size, keeping per-part accounting physical in that path too._build_s3_partsnow logsPART_PLAINTEXT_MISMATCHwhenever a client part's plaintext disagrees with the sum of its internal parts — this invariant violation is exactly what would have surfaced the bug immediately in prod.Tests
test_two_part_defer_tail_total_plaintext_not_double_counted— reproduces the prod 2-part copy shape; fails on main (part 1 plaintext ≠ stored internals; sidecar total = source + 1,129,664-analog), passes with the fix. Asserts sidecartotal_plaintext_size== source size and full GET roundtrip equality (content + order).test_defer_tail_flushed_on_complete_keeps_part_accounting— guards the complete-time flush path (part 1 only): per-part plaintext == internal parts, total == range span, GET roundtrip.Impact on the running backup
The ~1.14 TiB marked Failed on rack-12 is retryable — rclone never deletes the source on a failed move, and the same class of copy is pending retry on the other hosts (e.g. rack-2's 364 GiB of
companies). Once this deploys, the retry passes should complete the run.🤖 Generated with Claude Code