Filter concepts by levelShowing all levels.

System Design · Batch and Stream Processing

Batch and Stream Processing

Bounded jobs vs continuous processing, the vocabulary a never-ending stream needs, and the throughput/latency trade-off that creates backpressure

  • Batchbounded input, scheduled, exits when done
  • Streamunbounded input, runs continuously
  • Windowingtumbling, sliding, or session — bounds an endless stream
  • Checkpoint + replaysave state and offset together, resume from there

1.Batch vs stream processing

Batch processing runs a job over a bounded, already-collected set of data — yesterday's orders, a full table export — on a schedule or on demand, then finishes. Stream processing runs continuously over an unbounded sequence of events as they arrive, one at a time or in small groups, and never "finishes" in the same sense. The choice is about how fresh the result needs to be versus how much complexity you are willing to run permanently.

Key features

  • Batch input is bounded and known in advance (a file, a table snapshot, a fixed date range) — the job has a defined start and end.
  • Stream input is unbounded — the job starts once and keeps running, processing events as they arrive with no defined end.
  • Batch jobs are simpler to reason about and retry: rerunning the same bounded input produces the same result (idempotent by construction, if the job itself is deterministic).
  • Stream jobs trade that simplicity for freshness — results reflect events within seconds, not the hours or days a batch schedule implies.
  • Many production systems run both: a stream path for low-latency approximate results, and a nightly batch job that recomputes the exact answer over the full bounded dataset (the "lambda architecture" pattern).

Batch vs stream processing — the decision-relevant differences

Batch vs stream processing — the decision-relevant differences
DimensionBatch processingStream processing
InputBounded — a fixed dataset with a known endUnbounded — events keep arriving indefinitely
TriggerSchedule (cron, nightly) or on-demand jobContinuous — the job runs and never exits
Latency to resultMinutes to hours (matches the schedule)Sub-second to a few seconds
Failure recoveryRerun the whole job over the same bounded inputResume from a checkpoint — rerunning from scratch is often infeasible
Typical useBilling runs, data warehouse ETL, model trainingFraud detection, live dashboards, alerting
Bounded job vs continuous job

Batch

  • Input is a fixed file or table snapshot
  • Job starts, processes everything, exits
  • Rerunning the same input reproduces the same result

Stream

  • Input is an unbounded sequence of events
  • Job starts once and keeps running indefinitely
  • Recovery resumes from a checkpoint, not a full rerun
  • Batch
    • Input is a fixed file or table snapshot
    • Job starts, processes everything, exits
    • Rerunning the same input reproduces the same result
  • Stream
    • Input is an unbounded sequence of events
    • Job starts once and keeps running indefinitely
    • Recovery resumes from a checkpoint, not a full rerun

Command

text
// Batch: bounded input, runs once, exits
job.read(source="orders_2026_08_24.parquet")
   .aggregate(by="customer_id")
   .write(sink="daily_totals")
// process exits when the file is fully read

// Stream: unbounded input, runs forever
stream.read(source="orders-topic")
      .aggregate(by="customer_id", window="1h")
      .write(sink="running_totals")
// process keeps running as new events arrive

Batch processes a bounded, already-collected dataset on a schedule and then exits; stream processes an unbounded sequence of events continuously and never exits. Choose based on whether the result needs to be current within seconds (stream) or can wait for the next scheduled run (batch) — not based on which sounds more modern.

2.Windowing, state, checkpointing and replay

A stream never ends, so "sum all the orders" needs a boundary — a window is that boundary, a slice of time (or count) events get grouped into before an aggregate is computed. State is what a stream job remembers between events, such as a running total per customer. Checkpointing periodically saves that state (and how far into the input the job has read) so a crash does not lose it. Replay re-reads events from an earlier point, using a checkpoint to pick up exactly where processing left off instead of starting over.

Key features

  • Tumbling windows are fixed-size, non-overlapping (0-15min, 15-30min, ...) — every event belongs to exactly one window.
  • Sliding windows are fixed-size but overlapping (a 15-minute window advancing every 1 minute) — one event can fall into several windows at once.
  • Session windows have no fixed size — they close after a gap of inactivity (e.g. 30 minutes with no events from a given user), so window length varies per session.
  • State is what makes windowed aggregation possible at all — without it, a job could not accumulate a sum, count, or running total across the many events one window contains.
  • Checkpointing saves both the accumulated state and the input offset (how far the job has read) — the pairing is what lets a restarted job resume with correct totals instead of just correct position.
  • Replay depends on the source retaining events past the point they were first read — a source that discards events immediately after delivery (fire-and-forget) cannot support replay no matter how good the checkpoint is.

The three window types and when each fits

The three window types and when each fits
Window typeBoundary ruleExample use
TumblingFixed size, no overlap — each event in exactly one windowOrders per 15-minute block for a dashboard
SlidingFixed size, overlapping — one event can be in several windowsA trailing 5-minute error rate, recomputed every 10 seconds
SessionCloses after a gap of inactivity — variable length per keyA user's browsing session, closed after 30 minutes idle
From raw events to a recovered, checkpointed aggregate
grouped bytime/count/gapaggregatedintosaved everyN secondson crash, restartreads from herestate restored,offset resumed

Unbounded events

Windowing

groups events into a bounded slice

State

running aggregate per key

Checkpoint

saves state + offset periodically

Replay on restart

resumes from last checkpoint, not from zero

  • Unbounded events
    • leads to Windowing (grouped by time/count/gap)
  • Windowing — groups events into a bounded slice
    • leads to State (aggregated into)
  • State — running aggregate per key
    • leads to Checkpoint (saved every N seconds)
  • Checkpoint — saves state + offset periodically
    • leads to Replay on restart (on crash, restart reads from here)
  • Replay on restart — resumes from last checkpoint, not from zero
    • leads to State (state restored, offset resumed)

Command

text
// Tumbling window: 15-minute buckets, no overlap
stream.window(type="tumbling", size="15m")
      .aggregate(sum("order_total"), by="region")

// Sliding window: 5-minute window, advances every 30s
stream.window(type="sliding", size="5m", slide="30s")
      .aggregate(avg("latency_ms"))

// Session window: closes after 30 min of no events
stream.window(type="session", gap="30m")
      .aggregate(count(), by="user_id")

// Checkpoint every 10s: saves state + input offset
stream.checkpoint(interval="10s", store="s3://ckpts/")

Windowing turns an endless stream into bounded, computable slices — tumbling (fixed, no overlap), sliding (fixed, overlapping), or session (closes on a gap). State is the running aggregate a window needs; checkpointing saves that state together with the input offset; replay uses a checkpoint to resume from that point instead of from zero. Save state and offset together, or recovery silently under-counts.

3.Throughput, latency and backpressure in stream processing

Throughput is how many events a stream job processes per second; latency is how long one event takes to go from arrival to processed result. Batching events together raises throughput (fewer, larger operations) but raises latency (an event waits for its batch to fill). Backpressure is what happens when events arrive faster than the job can process them — a concern every stream processor must handle, since input never pauses to let it catch up on its own.

Key features

  • Throughput and latency are usually in tension: micro-batching (buffering a small window of events before processing) trades a few milliseconds of added latency for a large throughput gain, because per-event overhead is paid once per batch instead of once per event.
  • Batch processing optimizes almost entirely for throughput — no individual record's latency matters once the whole job is bounded by "when does the job finish."
  • Stream processing usually optimizes for low latency per event, which is why it processes events singly or in very small micro-batches rather than large batches.
  • Backpressure exists because a stream has no natural pause — unlike a batch job, which can simply take longer to read a fixed file, a stream source keeps producing events whether or not the consumer is ready.
  • A stream job under sustained backpressure without a way to signal or shed load ends up with unbounded buffer growth, which turns into out-of-memory failures or unbounded latency growth — the input queue keeps growing faster than it drains.
Per-event processing vs micro-batching

Per-event

  • Each event processed as soon as it arrives
  • Lowest possible latency per event
  • Per-event overhead paid on every single event — lower ceiling on throughput

Micro-batching

  • Events buffered briefly, processed as a group
  • Higher throughput — overhead amortized across the batch
  • Added latency: an event waits for its batch to fill or time out
  • Per-event
    • Each event processed as soon as it arrives
    • Lowest possible latency per event
    • Per-event overhead paid on every single event — lower ceiling on throughput
  • Micro-batching
    • Events buffered briefly, processed as a group
    • Higher throughput — overhead amortized across the batch
    • Added latency: an event waits for its batch to fill or time out

Command

text
// Per-event processing: lowest latency, lower throughput
stream.process(event -> handle(event))

// Micro-batching: higher throughput, added latency
stream.batch(size=500, maxWait="50ms")
      .process(batch -> handleBatch(batch))
      // each event now waits up to 50ms for its
      // batch to fill (or the wait to time out)

// Backpressure signal (consumer tells producer to slow down)
if queue.size() > HIGH_WATERMARK:
    consumer.pause()   // stop pulling more events
    // producer/broker now buffers instead of this job

Throughput (events/sec) and latency (time per event) trade off through batching — bigger batches raise throughput and raise per-event latency at the same time. Backpressure is what happens when input arrives faster than either mode's ceiling; a stream processor must handle it because, unlike a batch job's fixed file, the input never pauses on its own.

Remember

  • Batch processes a bounded, already-collected dataset on a schedule; stream processes an unbounded sequence of events continuously — pick based on required freshness, not novelty.
  • Windowing turns an endless stream into bounded, computable slices: tumbling (fixed, no overlap), sliding (fixed, overlapping), or session (closes on a gap).
  • Checkpointing must save state and input offset together — saving only the offset resumes position-correct but silently resets accumulated totals to zero.
  • Batching raises throughput and raises per-event latency at the same time; backpressure is what happens once input outruns either mode's ceiling.

Pro tip

  • Size a checkpoint interval against replay cost, not convenience — replaying past a source's retention window turns a crash into an unrecoverable outage.
  • Many production systems run both paths: a stream for low-latency approximate results, a nightly batch job that recomputes the exact answer over the full bounded dataset.

Next up

  • Backpressure
  • Distributed counters
  • Leaderboards and ranking

Sources

  • Google Cloud Dataflow streaming concepts — cloud.google.com/dataflow/docs
  • Apache Flink windowing docs — nightlies.apache.org/flink
  • Checked 2026-08-25

Spotted a mistake on this page? Tell us.