Stream large files instead of loading them fully into memory
coreintermediateReading 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.
Think of it as
Loading a whole file into memory is like trying to drink a lake by scooping it into one cup that has to hold the entire lake before you take a sip — the cup has to be exactly as big as the lake or it overflows. Streaming is drinking through a straw: water flows through a narrow, constant-size path, and you can drink a lake or a puddle with the same straw because the straw was never sized to the source.
What we're doing: Compare what happens to a worker process's memory when it loads a large upload fully vs streams it.
- 5
- request.read_all() forces the entire request body to exist in memory before line 6 can even start — nothing downstream can run until this line finishes.
- 6
- parse_csv(body) commonly allocates a second, parsed copy alongside the raw body, so peak memory can be a multiple of the file size, not just equal to it.
- 12
- request.stream_lines() yields one line at a time from the underlying connection — the next line is not read until this one has been processed and can be released.
Why this works: Streaming keeps memory use bounded by the size of one chunk, not the size of the whole file, because the process never needs more than one chunk resident at a time — the file's total size stops being a limiting factor for that worker's memory budget.
Reading an entire upload into a variable before validating or processing it
Wrong
Better
What you see: A handful of unusually large uploads (or a deliberately oversized one from an attacker) cause a worker process to be OOM-killed, taking down every other in-flight request on that worker at the same moment — with no error message pointing at the actual cause unless the process's own memory graph is checked.
Why: Checking body size after read_all() has already forced the full body into memory defeats the size check's own purpose — the memory spike that check exists to prevent has already happened by the time the length is known. Streaming lets the size limit abort mid-transfer, before the oversized remainder is ever read.
- 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
Loading fully into memory vs streaming, for the same file
Remember: 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.
See also: multipart chunking async and progress tracking · object storage vs application servers

