Large File Upload Platform
Production · Tata Consultancy Services, production work · Engineer, backend and client protocol
Browser uploads of large files tie up application servers, run into request-size limits, and give no guarantee that what landed in storage is what the user sent.
On this page
Overview
The browser splits a file into parts and uploads them concurrently to S3 using backend-issued presigned URLs. The API coordinates the upload without relaying those parts. After completion, the backend reads the stored object, computes its full-file SHA-256 and compares it with the digest calculated in the browser.
Problem
The original approach streamed files through the application server. A large upload held a request thread for minutes, request-size limits capped what users could send, and memory and bandwidth on the API tier scaled with file size rather than user count.
There was also no integrity guarantee. A truncated or corrupted upload could be stored and processed without anyone noticing.
Requirements
- Support files well beyond typical request-size limits
- Use the user's full bandwidth with parallel transfers
- Keep upload-part traffic off the application servers; read the completed object for verification
- Grant storage access that is scoped, temporary and per part
- Guarantee the stored object matches what the user sent
- Report meaningful progress to the user
Architecture
The API creates an S3 multipart upload and records a session. The browser uploads parts directly to S3 with bounded concurrency and presigned UploadPart URLs. It then sends the ordered part numbers, ETags and its full-file SHA-256 digest. After S3 completes the upload, the backend reads the completed object and hashes its bytes. Only a matching digest allows the file to become ready. This final readback is separate from the browser-to-S3 upload path.
- InitiateCreate upload session + multipart ID
- PresignShort-lived URL per part
- Upload partsDirect to S3, in parallel
- Part 1
- Part 2
- Part 3
- Part n
- CompletePart numbers + ETags sent to S3
- VerifyBackend reads object and compares full-file SHA-256
{
"uploadId": "2~x7Qp…",
"fileSize": 734003200,
"partSize": 16777216,
"parts": 44,
"concurrency": 4,
"completed": [1, 2, 3, 4, 5],
"inFlight": [6, 7, 8, 9],
"sha256": "9f2c41…e71a"
}Engineering decisions
- Direct-to-storage uploads
- The API does not relay upload parts. Verification still reads the completed object, so its object-read traffic, hashing work and latency scale with file size and must be budgeted separately.
- Hash on the client, verify on the server
- Independent hashing compares the original browser bytes with the completed object. Copying a client digest into metadata alone would not verify the stored content.
- Lifecycle rule for abandoned uploads
- Incomplete multipart uploads still cost storage. A bucket lifecycle rule aborts them automatically after a set period.
Challenges
Choosing part size
Small parts mean more requests and more URLs; large parts mean less parallelism and more lost work when a request fails. Part size is derived from file size, within S3's minimum part size and 10,000-part ceiling.
Hashing without freezing the browser
Hashing a large file on the main thread locks the UI. Incremental hashing off the main thread keeps progress smooth.
CORS and ETag exposure
The client needs each part's ETag to complete the upload, which requires the bucket CORS policy to expose that header explicitly.
Results
Qualitative outcomes; benchmark measurements are not published.
- Large files upload in parallel, directly to storage, beyond the size limits of a single request.
- Upload parts travel directly to S3; the backend reads the object once afterward for verification.
- Every stored file verified against a client-computed SHA-256 before use.
- Scoped, short-lived storage access instead of broad credentials.
Lessons learned
- Separate the control plane from the data plane early. It simplifies scaling and security at the same time.
- Get the protocol right first: a stable part plan is what retries and resumable uploads would build on later.
- Integrity checks are cheap insurance compared with debugging silently corrupted data.
Technical approach and performance notes
Technical approach
Chunking with a stable part plan
The client computes the part layout up front, so every part has a fixed number and byte range and parts can upload in any order.
Presigned URLs per part
Each URL authorises the UploadPart operation for a particular bucket, object key, upload ID and part number until expiry. It can be reused while valid; it is not single-use. Short expiry limits that access window.
Bounded parallelism
A bounded worker pool uploads several parts concurrently, with retries for failed parts. It limits simultaneous requests so transfer parallelism does not grow with the number of file parts.
End-to-end integrity
The browser calculates SHA-256 over the original file. After multipart completion, the backend reads the completed object, independently hashes its bytes and compares the two full-file digests. It does not compare the browser digest with an S3 multipart composite SHA-256, which is derived from part checksums.
Performance and scalability
- Throughput depends on client bandwidth, concurrency and request overhead; a reproducible throughput benchmark is not published here.
- Upload coordination scales with part count. Backend verification adds a completed-object read and hashing work proportional to file size.
- Presigned URLs are generated without extra network calls to storage, so they are cheap to issue.
Technologies used
- Java
- Spring Boot
- AWS S3
- Multipart upload
- Presigned URLs
- TypeScript