Filter concepts by levelShowing all levels.

System Design · File and Large Data Processing

File and Large Data Processing

Stream instead of loading fully into memory, chunk and track large transfers asynchronously, and keep blobs out of the database

  • Stream, don't loadpeak memory = chunk size, not file size
  • Chunk + asyncbound the work, don't block the request
  • Progress trackingwritten explicitly as chunks complete
  • Metadata vs blobsmall row references large bytes elsewhere

1.Stream large files instead of loading them fully into memory

Reading an entire file into a variable before processing it means the file's full size sits in RAM at once, even if you only ever look at it one line or chunk at a time. Streaming reads and processes a file in small, fixed-size pieces instead, so memory use stays roughly constant no matter how large the file is.

Key features

  • Memory use when loading fully scales with file size; memory use when streaming stays roughly constant, bounded by the chunk/buffer size, not the file size
  • A process that loads a 2 GB file into a variable needs at least 2 GB of free RAM for that one request, on top of whatever else the process is doing
  • An out-of-memory (OOM) kill from one oversized request can take down a whole worker process, dropping every other request it was concurrently handling, not just the offending one
  • Streaming APIs (Node.js Readable/Writable streams, Python file-like iterators, HTTP chunked transfer encoding) process data as it arrives or as it is produced, without requiring the whole payload to exist in memory at once
  • Streaming trades peak memory for a small amount of added complexity: you can no longer assume the whole file is available to seek around in freely — you generally process it front to back, one chunk at a time

Loading fully into memory vs streaming, for the same file

Loading fully into memory vs streaming, for the same file
PropertyLoad fully into memoryStream in chunks
Peak memory useScales with file sizeRoughly constant, bounded by chunk size
Behavior on a 10 GB fileLikely OOM kill on a typical workerSame memory footprint as a 10 MB file
Time to first byte processedWaits for the entire file to load firstStarts processing the first chunk immediately
Random access / seekingTrivial — the whole file is already in memoryLimited — usually front-to-back only
Processing a 5 GB upload: load fully vs stream

Load fully into memory

  • Whole 5 GB must fit in RAM before processing starts
  • Peak memory use = file size
  • A worker with 2 GB free gets OOM-killed

Stream in chunks

  • Each chunk (e.g. 64 KB) is processed and released
  • Peak memory use = chunk size, not file size
  • Same worker handles a 5 GB or a 5 MB file identically
  • Load fully into memory
    • Whole 5 GB must fit in RAM before processing starts
    • Peak memory use = file size
    • A worker with 2 GB free gets OOM-killed
  • Stream in chunks
    • Each chunk (e.g. 64 KB) is processed and released
    • Peak memory use = chunk size, not file size
    • Same worker handles a 5 GB or a 5 MB file identically

Command

text
// Loading fully -- memory use = file size
data = read_entire_file("upload.csv")   // 2 GB in RAM
process(data)

// Streaming -- memory use = chunk size, constant
for chunk in stream_file("upload.csv", chunk_size=64_KB):
    process(chunk)

Loading a file fully into memory makes peak memory scale with file size; streaming keeps peak memory bounded by chunk size instead. An oversized request that gets loaded fully can OOM-kill the whole worker process, not just fail that one request.

2.Multipart upload, chunking, asynchronous processing and progress tracking

Multipart upload splits a large file into independently-uploaded parts (system-design.object-storage.multipart-upload-and-signed-urls covers the S3-style mechanics in full — parts, upload IDs, completion). Chunking is the same idea applied more generally: any large payload, upload or download, gets processed in fixed-size pieces rather than as one unit. Asynchronous processing means the request that receives a file returns immediately, handing the actual work (transcoding, virus scanning, indexing) to a background job instead of making the caller wait for it. Progress tracking reports how much of a chunked, asynchronous operation has completed so far, since neither the caller nor a human watching a UI can tell from a single request/response pair how a multi-minute file operation is going.

Key features

  • Chunking generalizes multipart upload's pattern beyond just uploads: large downloads (HTTP range requests), large in-place transformations (video transcoding), and large batch writes are all commonly chunked for the same reason — bound the size of any one unit of work
  • Asynchronous processing separates "the file has been received" from "the file has been processed" — an upload endpoint that returns 202 Accepted with a job ID is telling the caller the second step has not happened yet, not lying about the first
  • A synchronous file-processing endpoint ties up a request-handling thread/connection for the full duration of the processing, not just the transfer — a 90-second video transcode makes the caller's HTTP connection wait 90 seconds unless processing is moved off that request
  • Progress tracking needs a place to store state the client can poll or subscribe to (a job record with a percentage or a completed/total chunk count) — the transfer itself does not automatically expose this, it has to be explicitly recorded as chunks complete
  • Combining all four: a large upload arrives in parts (multipart/chunked), the endpoint hands off to a queue immediately (asynchronous), and a job record's chunk-completed count (progress tracking) is what a status endpoint or WebSocket push reports back to the caller

The four mechanisms and what problem each one solves

The four mechanisms and what problem each one solves
MechanismProblem it solvesWhere it lives
Multipart uploadOne huge upload as a single unit is slow to retry and hard to parallelizeObject storage API — see system-design.object-storage.multipart-upload-and-signed-urls
Chunking (general)Any large transfer or transformation needs a bounded unit of work, not just uploadsUpload/download handlers, batch jobs, transcoding pipelines
Asynchronous processingProcessing a file can take far longer than a caller should wait on one requestA queue/worker that picks up a job after the request returns
Progress trackingA caller has no way to know how a multi-minute operation is goingA job record updated as chunks/steps complete, polled or pushed to the client
A large upload, end to end: chunked, asynchronous, tracked
chunk 1..Nhand off, donot blockprogressupdatespoll or push

Client

uploads in chunks

Upload endpoint

returns 202 + job_id immediately

Background job

processes chunks, updates progress

Status endpoint

chunks_done / chunks_total

  • Client — uploads in chunks
    • leads to Upload endpoint (chunk 1..N)
  • Upload endpoint — returns 202 + job_id immediately
    • leads to Background job (hand off, do not block)
  • Background job — processes chunks, updates progress
    • leads to Status endpoint (progress updates)
  • Status endpoint — chunks_done / chunks_total
    • leads to Client (poll or push)

Command

text
POST /uploads              -> 202 Accepted, { job_id: "j-9f2a" }
                               (upload handed to queue, not processed yet)

GET  /uploads/j-9f2a/status -> { status: "processing",
                                  chunks_done: 42, chunks_total: 120 }

GET  /uploads/j-9f2a/status -> { status: "complete",
                                  chunks_done: 120, chunks_total: 120 }

Multipart upload (system-design.object-storage.multipart-upload-and-signed-urls) is one specific application of the broader pattern: chunk large transfers, process them asynchronously so the request does not block on the work, and track progress explicitly by writing state as chunks complete — none of this is automatic.

3.Separate metadata from blobs

Metadata (a file's name, size, owner, content type, upload date) is small and structured — it belongs in a database row. A blob (the actual file bytes) is large and opaque to the database — it belongs in object storage. Storing the blob itself as a database column instead of a reference to it makes routine database operations slow and expensive for no benefit, because the database ends up carrying weight it was never built to carry.

Key features

  • A "blob" here means the actual file bytes — a photo, video, PDF, backup archive — as opposed to data about that file
  • Storing a blob in a database column (e.g. Postgres bytea, MySQL BLOB) makes every backup, replication, and index operation touch that blob's full size, even for tables and queries that never needed it
  • A metadata row instead stores a reference — typically the object storage key or URL, plus size, content type, owner, and timestamps — all of it small, fixed-shape, and fast to query or index
  • Separating them means the database stays small and fast (metadata rows are typically well under 1 KB) regardless of how large or numerous the underlying files are
  • This is the same principle system-design.object-storage.object-storage-vs-application-servers applies at the request-path level (route large files around the app server); this concept applies it at the storage layer (route large files around the database)

Blob in the database vs blob in object storage, metadata row referencing it

Blob in the database vs blob in object storage, metadata row referencing it
AspectBlob stored in a DB columnBlob in object storage + metadata row
Row sizeGrows with file size — a 500 MB video is a 500 MB rowFixed and small — typically under 1 KB regardless of file size
Backup/replication costEvery backup and replica copies the full blobBackup copies only the small reference row
Query performanceScanning or indexing the table competes with blob I/OMetadata queries stay fast — no blob bytes in the table
Serving the fileApp server reads the blob out of the database firstClient can be handed a direct link (e.g. a signed URL) to the object store
The row stays the same size no matter how large the file is

The row holds a key, not the bytes. That is what keeps backups, replicas and index rebuilds cheap.

  • On the left, a database row for an uploads table listing id, owner_id, object_key, content_type and size_bytes — under 1 KB, and fixed regardless of the file.
  • An arrow labelled object_key points from that row to the right-hand box, object storage.
  • On the right, object storage holds the actual file, inv-4471.pdf, whose bytes may be 10 KB or 10 GB. It is served by a signed URL and never read back through the database.
  • Below: store the blob in the row instead and every backup, replica and index rebuild has to move it too.

Command

sql
-- Metadata row: small, fixed-shape, fast to query
CREATE TABLE uploads (
  id           UUID PRIMARY KEY,
  owner_id     UUID NOT NULL,
  object_key   TEXT NOT NULL,   -- reference into object storage
  content_type TEXT NOT NULL,
  size_bytes   BIGINT NOT NULL,
  uploaded_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- The file's actual bytes live in object storage at object_key,
-- never in this table.

Metadata (small, structured — name, size, owner, a reference key) belongs in a database row; the blob itself (large, opaque bytes) belongs in object storage. Storing the blob directly in the database makes every backup, replica, and query pay for its full size.

Remember

  • Loading a file fully into memory makes peak memory scale with file size — an oversized request can OOM-kill a whole worker, not just fail on its own.
  • Streaming keeps peak memory bounded by chunk size, so the same handler works identically on a 5 MB file and a 5 GB one.
  • Asynchronous processing decouples "received" from "processed" — a request should not block on work that can take minutes.
  • Metadata (small, structured) belongs in a database row; the blob itself belongs in object storage, referenced by key.

Pro tip

  • Progress is never automatic — a background worker has to explicitly write a chunks-done count as it goes, or a status endpoint has nothing real to report.
  • For the multipart upload mechanics themselves (parts, upload IDs, completion), see system-design.object-storage.multipart-upload-and-signed-urls — this section covers the surrounding pattern, not a second copy of that API.

Next up

  • API design
  • Rate limiting
  • Caching fundamentals

Sources

  • MDN Streams API; AWS Amazon S3 multipart upload and object metadata guides
  • Checked 2026-08-25

Spotted a mistake on this page? Tell us.