fix: harden S3 multipart uploads against CompleteMultipartUpload InvalidPart - #1631
fix: harden S3 multipart uploads against CompleteMultipartUpload InvalidPart#1631sylr wants to merge 3 commits into
Conversation
…lidPart Full and incremental backups (and cold-storage archives) upload via the aws-sdk-go-v2 manager Uploader with all defaults. Against the customer S3 path a full backup of a multi-GB Pebble checkpoint blob fails at CompleteMultipartUpload with `InvalidPart: One or more of the specified parts could not be found`, and never completes. Two changes: - Set the multipart PartSize to 32 MiB (default 5 MiB) on both the backup and cold-storage uploaders. This cuts the part count ~6.4x for large objects — smaller failure surface for a part to go missing, and lifts the 10,000-part single-object ceiling from ~48.8 GiB to ~312 GiB. Per-part memory stays bounded (partSize x concurrency). - Set RequestChecksumCalculation=WhenRequired on the shared S3 client (coldstorage.NewS3Client, reused by the backup client). The recent aws-sdk-go-v2 default (WhenSupported) attaches a trailing CRC via aws-chunked encoding to every UploadPart; this is the suspected trigger for the InvalidPart at Complete, and the older binary that predates the default uploaded fine. End-to-end integrity is unaffected: checkpoint object keys are content-addressed by sha256 and cold-storage archives carry a verified sha256 metadata checksum. Constraint: bucket, KMS key and real S3 proven healthy for multipart from a clean path — failure is client/path-side, so the fix targets the client Rejected: application-level whole-object retry only | does not address a deterministic (non-transient) InvalidPart; kept as a follow-up Rejected: extend hash chain / change integrity model | out of scope, sha256 content-addressing already covers object integrity Confidence: medium Scope-risk: narrow Directive: if InvalidPart persists after this, the next lever is the checksum path — capture a wire trace of UploadPart/CompleteMultipartUpload before reverting the checksum default Not-tested: live multi-GB multipart upload against the customer S3 path (no repro from a clean network path)
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🛑 Changes requested — automated reviewThe PR raises the S3 multipart upload part-size threshold to 32 MiB and correctly adds a test-only 5 MiB part-size override for the backup-package multipart tests. However, the equivalent cold-storage regression test ( |
NumaryBot
left a comment
There was a problem hiding this comment.
NumaryBot posted 1 new inline finding.
Summary: #1631 (comment)
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## release/v3.0 #1631 +/- ##
================================================
- Coverage 74.75% 74.66% -0.09%
================================================
Files 446 446
Lines 47485 47485
================================================
- Hits 35497 35455 -42
- Misses 8777 8814 +37
- Partials 3211 3216 +5
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
… parts Raising the production part size to 32 MiB routed the existing 12 MiB multipart tests through a single PutObject, leaving the multipart path this PR hardens unexercised in CI (NumaryBot finding). Add an unexported newS3StorageWithPartSize constructor and have the two multipart tests force a 5 MiB part size, so their 12 MiB payloads still exercise CreateMultipartUpload/UploadPart/CompleteMultipartUpload regardless of the production default. Confidence: high Scope-risk: narrow Not-tested: run requires the MinIO testcontainer (Docker) — compile-checked locally, executed in CI
NumaryBot
left a comment
There was a problem hiding this comment.
NumaryBot posted 1 new inline finding.
Summary: #1631 (comment)
Same fix as the backup uploader: with s3UploadPartSize raised to 32 MiB, TestS3Storage_ArchiveMultipartLargeObject's 12 MiB payload fell onto the single-PutObject path, leaving the multipart archive path unexercised (NumaryBot finding). Add an unexported newS3StorageWithPartSize and force a 5 MiB part size in that test so it still drives CreateMultipartUpload/UploadPart/CompleteMultipartUpload. Confidence: high Scope-risk: narrow Not-tested: run requires the MinIO testcontainer (Docker) — compile-checked locally, executed in CI
NumaryBot
left a comment
There was a problem hiding this comment.
NumaryBot posted 1 new inline finding.
Summary: #1631 (comment)
| return newS3StorageWithPartSize(client, bucket, s3UploadPartSize) | ||
| } | ||
|
|
||
| // newS3StorageWithPartSize is NewS3Storage with an explicit multipart part |
There was a problem hiding this comment.
🟠 [major] Cold-storage multipart test no longer exercises the multipart upload path
reported by NumaryBot, codex
With s3UploadPartSize raised to 32 MiB, TestS3Storage_ArchiveMultipartLargeObject uses a 12 MiB payload, so the AWS SDK uploader silently falls through to the single PutObject path instead of CreateMultipartUpload / UploadPart / CompleteMultipartUpload. The backup package tests received a test-only 5 MiB part-size override to compensate, but no equivalent treatment was applied to the cold-storage test, leaving the hardened multipart path entirely unexercised in CI.
Suggestion: Either raise the cold-storage test payload above 32 MiB, or inject a test-only part-size override (e.g. 5 MiB, matching the backup test approach) so that TestS3Storage_ArchiveMultipartLargeObject actually exercises the CreateMultipartUpload / UploadPart / CompleteMultipartUpload code path this PR is intended to harden.
|
Reviewed live head |
Problem
Full S3 backups of a large ledger fail with
CompleteMultipartUpload … InvalidPart: One or more of the specified parts could not be found. Investigation against the customer's dev bucket showed the bucket/KMS/S3 are healthy for multipart from a clean path — the failure is specific to the client's multipart execution, on files large enough to go multipart.Change
internal/infra/backup/s3.go) and cold-storage (internal/infra/coldstorage/s3.go) uploaders (SDK default is 5 MiB). Cuts part count ~6.4× for multi-GB objects (smaller failure surface) and lifts the 10,000-part single-object ceiling from ~48.8 GiB to ~312 GiB.RequestChecksumCalculation=WhenRequiredon the shared S3 client (coldstorage.NewS3Client, reused by the backup client). Recentaws-sdk-go-v2defaults toWhenSupported, which attaches a trailing CRC viaaws-chunkedencoding on every part — the leading suspect for theInvalidPartat Complete, and the older binary that predates the default uploaded fine.Object integrity is preserved end-to-end without the SDK checksums: checkpoint keys are content-addressed by sha256, and cold-storage archives carry a verified sha256 metadata checksum.
Verification
go build -tags s3 ./...andgo vetclean.Notes