Batch vs Streaming: Intermediate
Keep latency and throughput apart by asking what each one answers. Latency answers how old is the newest row the consumer can see. Throughput answers how many rows per second can this pipeline absorb before falling behind. Neither number tells you the other.
| Pipeline | Input rate | Throughput | Latency (p95) |
|---|---|---|---|
| Nightly orders load | 4M rows/day | 80,000 rows/s during the run | up to 24 h |
| Hourly inventory sync | 4M rows/day | 6,000 rows/s during the run | up to 65 min |
| Click stream | 4M rows/day | 50 rows/s, all day | 3 s |
Which one is holding you back
- ▸Backlog (rows waiting, or queue depth) grows run after run: throughput. The pipeline cannot keep pace.
- ▸Backlog drains to 0 after each run but the consumer still complains: latency. The schedule is the limit.
- ▸Run time grows in proportion to input size: throughput. More rows, more minutes.
- ▸Run time stays flat while the consumer's freshness need shrinks: latency. The requirement changed.
The same pipeline can switch categories: an hourly job that processes 10GB today is latency-constrained; when the input triples it becomes throughput-constrained with no code change.
Read the result as a pair of signals. If rows_loaded spikes in a single hour and p95_latency_s sits near 86,000 in that same hour, you are looking at a nightly batch and the consumer is seeing day-old rows. If rows_loaded is smooth across all 24 hours and p95_latency_s is climbing hour over hour, rows are arriving faster than they are written and the backlog is growing.
A pipeline reads a queue every hour and writes to a curated table. Each run takes 48 minutes, up from 30 minutes 3 months ago, and the queue depth at the start of each run has been rising for weeks. The consumer wants rows within 2 hours. Which metric constrains this pipeline, and what change helps?
Each tick, the trigger job reads all events appended to the queue since the last tick and writes them as a single batch.
- Discrete runs, each with a start, an end and one output
- Fixed per-run overhead shared across many rows
- A run can be repeated by rereading the same queue range
- A queue as the source rather than a daily file or table snapshot
- A job that never exits and never waits for a scheduler
- Freshness measured in seconds or minutes rather than hours
The interval sets the latency
| Trigger interval | Processing per run | Worst-case latency | Runs per day | Rows per run |
|---|---|---|---|---|
| 1 h | 14 min | 74 min | 24 | 166,667 |
| 5 min | 75 s | 6 min 15 s | 288 | 13,889 |
| 1 min | 20 s | 80 s | 1,440 | 2,778 |
| 10 s | 8 s | 18 s | 8,640 | 463 |
Fixed write overhead, shared across a batch
A micro-batch job on a 30 second trigger processes a typical batch in 12 seconds, but between 18:00 and 19:00 the input quadruples and each batch takes 45 seconds. The consumer needs rows within 2 minutes. What happens during that hour, and what is the smallest fix?
| Line item | Nightly batch | Streaming | Ratio |
|---|---|---|---|
| Compute, 4M rows/day | 4 machines x 1 h = 4 machine-hours | 3 machines x 24 h = 72 machine-hours | 18x |
| Peak sizing | Sized for the whole day's rows in 1 h | Sized for the busiest second, idle the rest | n/a |
| Writes to curated layer | 1 commit per day | 1 commit per second = 86,400 | 86,400x |
| Always-on queue | None, reads yesterday's table | Queue cluster running 24 h | new line item |
| People | Failures found next morning | Pager at 03:00 for a stalled job | on-call rota |
3 places the premium hides
- Idle computethe 24 hour taxMachines sized for the busiest second run through the quietest hour. A pipeline whose input is 10x higher at noon than at 04:00 wastes most of its capacity most of the night.
- Per-write overheadfixed cost multiplied by frequencyEach commit to the curated layer costs roughly the same whether it carries 1 row or 1M. Committing every second pays that cost 86,400 times a day, and readers then pay again to open thousands of tiny outputs.
- Operationsa system that is always running can always be downA nightly job that fails at 02:00 is rerun at 09:00 by whoever arrives first. A stream that stalls at 02:00 is 7 hours behind by 09:00, so someone carries a pager.
Pricing the delay
| Consumer | Cost of 1 hour of staleness | Freshness that removes it | Pattern that earns its premium |
|---|---|---|---|
| Card fraud decision | Order value per fraudulent order | Seconds | Streaming |
| Warehouse stock on product page | Refund per oversold unit | Minutes | Micro-batch |
| Marketing spend dashboard | A mistimed budget shift, once a day at most | Hours | Hourly batch |
| Monthly finance close | Nothing | Days | Nightly batch |
- The consumer is a machine, or a person acting within minutes
- Each minute of staleness has a cost you can write down
- Input arrives steadily, so machines are rarely idle
- The consumer reads on a schedule: a daily report, a weekly review
- Staleness costs nothing until a threshold measured in hours
- Input arrives in daily dumps, so the stream waits on an upstream batch anyway
A team proposes replacing a nightly batch that feeds a weekly executive revenue deck with a streaming pipeline. The batch costs $40 a day; the stream estimate is $600 a day. The deck is opened on Monday mornings only. Which judgement is right?
| Transform | Needs earlier records? | Class |
|---|---|---|
| Parse event JSON into columns | No | Stateless |
| Filter out internal test users | No | Stateless |
| Look up country from a static IP table | No | Stateless |
| Count orders per city per 5 minute window | Yes, every order in the window | Stateful |
| Drop an event whose id was seen in the last hour | Yes, every id for 1 hour | Stateful |
| Match each refund to its original payment | Yes, every unmatched payment | Stateful |
| Running lifetime spend per customer | Yes, all history | Stateful |
| event_id | city | amount | event_ts |
|---|---|---|---|
| e1 | Austin | 40 | 10:01:12 |
| e2 | Austin | 25 | 10:03:40 |
| e3 | Denver | 60 | 10:04:05 |
| window_start | city | orders | total |
|---|---|---|---|
| 10:00 | Austin | 2 | 65 |
| 10:00 | Denver | 1 | 60 |
The right table cannot be produced one row at a time. When e1 arrives, the pipeline knows Austin has 1 order so far; it must hold that partial count somewhere until 10:05, when the window closes and the row is final. In a batch job this is a GROUP BY, and the state lives in the memory of a run that ends in minutes. In a stream the job never ends, so state lives in a store that must survive for as long as the window is open, for every open key at once.
State costs memory and time
Recovering state after a crash
- ▸Stateless: resume from queue position 48,210, reprocess nothing, 2 seconds to catch up.
- ▸Stateful, no checkpoint: all partial counts gone; the 10:00 window undercounts and nobody is told.
- ▸Stateful, checkpoint every 5 s: load state and position 38,210 saved at 10:02:55, replay 10,000 events, 5 seconds to catch up, counts correct.
Classify this rule and say what it needs: flag any payment card used in 3 different cities within 10 minutes.
| Consumer freshness tier | Smallest pattern that meets it | Cost relative to nightly batch |
|---|---|---|
| Yesterday's numbers (24 h) | Nightly batch | 1x |
| This hour's numbers (1 h) | Hourly batch over new rows only | 1x to 2x |
| Within a few minutes | Micro-batch, 1 to 5 min trigger | 3x to 10x |
| Within seconds | Streaming, stateless where possible | 15x and up |
A worked diagnosis
A nightly job rebuilds a 2 year curated.orders table from the full raw history every night at 01:00. 2 years ago it took 90 minutes. It now takes 5 hours 40 minutes and must finish before 07:00, when the finance team opens its dashboard. Meanwhile the operations team has asked for the same table within 1 hour so it can react to regional delivery delays. Run time growing in step with history is the throughput signal; the 1 hour request is the latency signal. The job has both problems, and the same rung fixes both.
One hourly run feeds both consumers from the same curated table.
Moving one path to micro-batch or streaming
- Name the constraint first: a growing backlog is throughput, a waiting consumer with idle machines is latency
- Process only new rows before shortening the schedule
- Move one consumer's path up a rung, never the whole table
- Shorten a schedule on a throughput-bound job; extra runs add overhead, not speed
- Rebuild as streaming because the dashboard is behind
- Put a stateful aggregate into a stream before pricing its state and checkpoints
A daily batch loads 20M click rows in 50 minutes for a marketing dashboard viewed at 09:00. Product wants a live board of clicks per page over the last 5 minutes, refreshed within 1 minute. Marketing's need is unchanged. Which design is the smallest that meets both?
> A grocery delivery company runs a nightly batch that rebuilds its orders table from 2 years of history for the finance dashboard. The run has crept to 6 hours. Dispatch now wants courier delays per zone within 1 minute, and the payments team wants a card check that blocks stolen cards before the order is accepted.
Measure latency and throughput as 2 separate numbers, then buy exactly as much freshness as the consumer can use.
- Category
- Pipeline Architecture
- Difficulty
- intermediate
- Duration
- 30 minutes
- Challenges
- 11 hands-on challenges
Topics covered: Latency vs Throughput Tradeoff, Micro-Batch: The Middle Ground, Why Streaming Costs More, Stateful vs Stateless Transforms, When Batch Outgrows Itself
Lesson Sections
- Latency vs Throughput Tradeoff
Latency is the time between an event happening and its result reaching the consumer. Throughput is how much data the pipeline processes per unit of time, measured in rows per second or gigabytes per hour. A pipeline can have high throughput and terrible latency: a nightly job that chews through 180GB in 50 minutes moves data fast, yet an order placed at 00:05 waits almost 24 hours before a dashboard shows it. All 3 pipelines carry the same 4M rows a day. The nightly load has the highest throughp
- Micro-Batch: The Middle Ground
Micro-batch is batch run on a short, fixed timer against a queue. Every trigger interval, say 60 seconds, the job wakes, reads everything that arrived on the queue since the previous trigger, transforms it, writes one output, records how far it read, and sleeps until the next tick. The interval sets the latency Worst-case latency for a row is the full interval plus the processing time of the batch it lands in. A row that arrives 1 second after a tick waits 59 seconds for the next one, then waits
- Why Streaming Costs More
A batch job rents machines for the minutes it runs. A streaming job rents them for every minute of the day, including the hours when almost nothing arrives. That difference drives most of the cost gap. The compute line surprises people in the other direction: streaming uses fewer machines at any moment than the batch job at its peak, because the work spreads across the day instead of piling into 1 hour. It still costs 18 times the machine-hours because those 3 machines never stop. Reserved prici
- Stateful vs Stateless Transforms
A stateless transform produces its output for a record from that record alone: each row goes in, its row comes out, and the transform remembers nothing. A stateful transform's output depends on records it has already seen, so it needs memory of the past to produce the present. One question sorts any transform: does its output need earlier records? State costs memory and time State costs memory in proportion to the number of open keys times the size of each key's partial result, and it costs time
- When Batch Outgrows Itself
Batch pipelines outgrow themselves in 2 ways, and the fix depends on which. The data grows until the run no longer finishes inside its schedule, a throughput problem. Or the consumer's freshness need shrinks until the schedule itself is too slow, a latency problem. Both show up as the same complaint, the dashboard is behind, and both tempt a team to rebuild as streaming. Name the problem first, then climb the smallest number of rungs that meets the consumer's tier. A worked diagnosis Most of the