Engineering 2 TB File Transfers with S3 Multipart Uploads
- AWS S3
- Java
- Resilience
Moving a 2 TB file over HTTP in a single request is a non-starter: one dropped connection and you begin again from zero. The fix is to stop thinking about "a file" and start thinking about thousands of independent parts that can fail and retry on their own.
Why single-request uploads break
A naïve PUT of a huge object couples the entire transfer to one TCP
connection and one process lifetime. At terabyte scale you will hit timeouts,
memory pressure from buffering, and the brutal reality that a failure at 99%
costs you everything.
Multipart, conceptually
S3 multipart upload splits the object into parts (5 MB–5 GB each). Each part is uploaded independently and S3 stitches them together on completion:
- Initiate — ask S3 for an
uploadId. - Upload parts — send each chunk in parallel; S3 returns an
ETagper part. - Complete — send the ordered list of
{ partNumber, eTag }.
Because parts are independent, a failed part is a local retry — not a restart.
Making it resilient
The multipart primitives give you the mechanism; resilience comes from how you wrap them:
- Persist progress. Store the
uploadIdand each completed part'sETagin a table keyed by the transfer. On restart, skip parts you've already sent. - Bounded parallelism. Upload N parts concurrently with a semaphore so you saturate bandwidth without exhausting memory or file handles.
- Retry with backoff. Treat
5xxand throttling as retryable; use exponential backoff with jitter so a thousand parts don't retry in lockstep. - Resume, don't restart. A crashed worker should be able to pick up an
in-flight
uploadIdand finish the remaining parts. - Abort orphans. A scheduled job that calls
AbortMultipartUploadon stale uploads keeps you from paying for parts that never completed.
The mindset shift: a transfer isn't an event, it's a small state machine you can pause, resume, and reason about.
What this bought us
With persisted part state and bounded concurrency, a mid-flight node failure turns into a few retried parts instead of a restarted terabyte — and transfers survive deploys, network blips, and process restarts.
If you're building large-file infrastructure, start with the state model, not the upload call. The upload is the easy part.