Back
System Design

Building Reliable Upload Systems

Chunked uploads, progress tracking, retry logic, and why the naive approach fails at scale.

2026-06-0510 minintermediate
#upload#architecture#node#storage
EST CONFIGNo response
326 words · 100 lines
Xonoxc

Problem

Building an upload system seems trivial: accept a file, save it to disk/S3. The complexity appears when you need to handle:

  • Network interruptions
  • Large files (2GB+)
  • Concurrent uploads
  • Progress tracking
  • Virus scanning
  • Multipart encoding
  • Rate limiting

Naive Approach

The simplest version:

app.post("/upload", async (req, res) => {
  const file = req.files?.file
  await fs.promises.writeFile(`/uploads/${file.name}`, file.data)
  res.json({ 
    success: true 
  })
})

This fails for any file over 50MB, blocks the event loop, and offers zero resilience.

Architecture

Client → Chunk Splitter → Upload API → Chunk Assembler → Scan Queue → Storage
           ↓                                                             ↑
      Progress Tracker                                            CDN Invalidator
           ↓
      Client Polling / WebSocket

Problems Encountered

Chunk Boundary Alignment

[!] Pitfall

I naively split files into fixed 5MB chunks. This broke because:

  • S3 multipart upload has a 10000 part limit
  • Some chunks were too small (S3 minimum is 5MB except the last)
  • Retrying a chunk meant re-uploading arbitrary byte ranges

The fix was to use adaptive chunk sizing. Start with 5MB, adjust based on file size to stay under 10000 parts.

Progress Tracking Accuracy

> Key Insight

Progress should be calculated at the chunk level, not the byte level:

function calculateProgress(upload: Upload): number {
  const completedBytes = upload.completedChunks
    .reduce(
        (sum, c) => sum + c.size,
        0
    )

  return completedBytes / upload.totalSize * 100
}

Simple average based on chunks completed, weighted by chunk size.

Tradeoffs

[ Tradedoffs.md ]
StrategyReliabilityLatencyStorageComplexity
Single POSTLowLowTempMinimal
Chunked + RetryHighMediumTempHigh
StreamingMediumLowNoneMedium
Resumable (TUS)Very HighMediumTempVery High

Final Mental Model

Reliable uploads are state machines disguised as file transfers.