All work

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.

My contribution
Designed the upload protocol, built the backend endpoints for session, presigning and completion, and the client-side chunking, parallel upload and integrity logic.
Key challenge
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.
Outcome
Upload parts bypass the application servers. After completion, the backend reads the stored object and checks its SHA-256 against the client’s digest before the file is ready.
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.

Large File Upload Platform architectureThe backend creates a multipart upload and signs each part URL. The browser uploads parts directly to S3 in parallel, then sends part ETags and its full-file SHA-256 to the backend. After completion, the backend reads and hashes the whole object and compares digests: a match makes the file ready; a mismatch fails verification.BackendBrowser1. Start upload2. Sign part URLsS33. Parallel PUTs4. CompleteAfter all PUTs+ SHA-256Read full object5. Verify SHA-256Match: readyMismatch: reject
Architecture sketch. Solid arrows are the main path, dashed arrows run only when needed, and the outlined box marks the key engineering decision.
  1. InitiateCreate upload session + multipart ID
  2. PresignShort-lived URL per part
  3. Upload partsDirect to S3, in parallel
    • Part 1
    • Part 2
    • Part 3
    • Part n
  4. CompletePart numbers + ETags sent to S3
  5. 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"
}
Illustrative upload session mid-transfer. Values are examples.

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