System Design · Data Processing
Batch Processing Algorithms
Forty billion events landed yesterday. Finance needs revenue by SKU, ML needs user features, and compliance needs a closed ledger. None of that must update mid-scroll—but it must finish before morning with correct aggregates. That is : finite inputs, staged algorithms, durable intermediate results.
This note is the algorithm catalog behind those jobs: , , , hash and sort-merge joins, combiners, skew, and recovery via / lineage. Stream processing is the continuous cousin; here the dataset has an end.
One nightly aggregate
Parallel map over file splits or table partitions.
Pre-aggregate when the reduce is associative.
Move partials to the reducer that owns the key.
Reduce / write
Final aggregate, join, or sort into output files.
What actually happens
Batch engines schedule a DAG (or a linear MapReduce job) over . Embarrassingly parallel map/filter stages need little coordination. Group-by, join, and global sort force a : data is repartitioned by key, often spilled through . That shuffle is where cost and skew live.
Joins pick an algorithm from sizes and memory: broadcast , partitioned/grace hash join, or . Combiners and partial aggregations shrink bytes before they cross the network. Failures replay from durable map outputs or from lineage; explicit trade rewrite cost for shorter recovery.
The product promise is different from streaming: you optimize for correct completion by a deadline (SLA), not for millisecond watermarks. Micro-batches blur the line; the same algorithms still sit under the stage planner.
How to choose batch algorithms
1. Embarrassingly parallel? Map/filter/project only—no shuffle. Keep it that way if the business question allows.
2. Aggregate or join by key? Budget shuffle. Add a combiner if associative; pick hash versus sort-merge from memory and sortedness.
3. Skewed keys? Plan salting or skew joins before the first 8-hour incident.
Simple rule of thumb
Prefer fewer shuffles, smaller shuffles, and even partitions. Materialize when retry cost exceeds write cost. Treat p99 task time as the stage clock.
MapReduce as the template
Read the diagram definition
flowchart LR
In[Input splits] --> Map[Map tasks]
Map --> Comb[Optional combiner]
Comb --> Shuf["Shuffle / sort by key"]
Shuf --> Red[Reduce tasks]
Red --> Out[Output partitions]
made the template explicit. Map emits intermediate key/value pairs. An optional reduces locally. The framework partitions by key, sorts each partition (often via ), and calls reduce once per key (or per key group). of map outputs is why a dead reducer does not force every mapper to rerun from source—though a dead mapper still does.
Word count (classic):
map(doc):
for word in tokenize(doc): emit(word, 1)
combine(word, counts[]): # optional, associative
emit(word, sum(counts))
reduce(word, counts[]):
emit(word, sum(counts))
Cost intuition:
map CPU ∝ input bytes
shuffle bytes ≈ size of map output after combine
reduce CPU ∝ per-key group size
stage wall time ≈ max(task times) # stragglers dominateModern engines (Spark, Flink batch, BigQuery, warehouse SQL) hide the API but keep the stages: exchange operators are shuffles; hash aggregates are combiners plus final merge; sort operators are external sorts. Reading a query plan is reading MapReduce in nicer clothes.
External sort and why reducers wait
When intermediate data exceeds RAM, engines sort with replacement-selection or quicksort-sized runs, write runs to disk, then k-way merge. Network shuffle often lands unsorted chunks that must be sorted before a streaming reduce can assume key order. That is why “CPU is fine but the job is slow” often means disk-bound merge.
External sort sketch:
while input remains:
fill memory with records
sort → write run_i to disk
merge runs with a k-way heap (or multi-pass if too many runs)
I/O lower bound: roughly 2 passes over data for sort
(write runs + read for merge), more if merge is multi-level
Design knobs:
more memory → fewer/larger runs → cheaper merge
more spill disks / better local SSDs → faster shuffle
fewer giant partitions → less per-task sort pressureJoin algorithms
Read the diagram definition
flowchart TD
J[Equi-join two datasets] --> Fit{Smaller side fits memory?}
Fit -->|Yes| HJ[Broadcast / in-mem hash join]
Fit -->|No| Part[Partition both on join key]
Part --> Fit2{Per-partition build fits?}
Fit2 -->|Yes| GH[Grace / partitioned hash join]
Fit2 -->|No| SM[Sort-merge join]
HJ --> Skew{Key skew?}
GH --> Skew
SM --> Skew
Skew -->|Yes| Salt[Salt hot keys / skew join]
Skew -->|No| Done[Run stage]
Salt --> Done
builds on the build side and probes with the stream side. Broadcast hash join ships the small side to every executor—excellent until the “small” side is not. Grace/partitioned hash join first partitions both inputs on the join key so each partition’s build fits memory.
sorts both sides and merges. It shines when inputs are already sorted (storage order, prior stage) or when hash tables would thrash. Many optimizers switch strategies with statistics; bad stats pick broadcast for a 200 GB “dimension” and melt executors.
| Algorithm | Best when | Watch out |
|---|---|---|
| Broadcast hash | Build side fits executor memory | Misestimated size → OOM |
| Partitioned hash | Both large; enough memory per part | Skewed key → one fat partition |
| Sort-merge | Huge sides; ordered output useful | Sort I/O cost; still skew-sensitive |
| Skew / salted join | Known hot keys | Extra fan-out and bookkeeping |
Combiners, partial aggregates, and associativity
A is a map-local reduce. Sums, counts, min/max, and sketch merges (HLL union) are safe. Average is not a safe one-shot combine unless you emit (sum, count). Distinct counts need sketches or a two-phase unique-then-count. Getting this wrong silently returns plausible wrong numbers—the worst batch failure mode.
Safe combine: sum, count, min, max, HLL.merge
Unsafe one-shot: mean, median, distinct count (exact)
Pattern for mean:
map → emit (key, sum=x, count=1)
combine/reduce → sum sums, sum counts
final → sum/count
Two-phase distinct:
stage1: map unique (key, user) → shuffle → distinct users per key
stage2: count per key
or: use HyperLogLog merge in one stage if approx OKSkew, stragglers, and barriers
Stages are : the slowest task defines finish time. A from key skew (one user_id with 5% of events) holds everyone. Mitigations: better partitioners, key salting with fan-in, range repartition from sampled quantiles, speculative execution of slow tasks, and isolating hot keys into dedicated reducers.
Also watch input skew: a few huge files versus millions of tiny ones. Compact small files in a prior batch; split large ones. Scheduling overhead and namenode/listing cost are real batch algorithms problems, not just “ops.”
Fault tolerance: materialize or recompute
Classic MapReduce materializes map output. Spark’s lineage recomputes lost partitions until you or persist at a wide dependency. Warehouse tables materialize each CREATE TABLE AS SELECT. Choose based on recomputation cost versus storage and on how often nodes die mid-flight.
Recovery cost sketch:
Job 4 hours, failure at 3:50 without checkpoint
lineage recompute of expensive shuffle ≈ hours
Checkpoint after heavy shuffle (~30 min write):
failure at 3:50 → replay ~10 min of tail stages
Rule:
checkpoint after expensive shuffles you refuse to redo
do not checkpoint every tiny narrow transform
Exactly-once batch outputs:
write to temp + commit (manifest rename / table transaction)
never append partially visible files as “done”Worked micro-estimate
Daily events: 40e9 rows × 200 B ≈ 8 TB compressed input 2 TB
Group by (sku, country), sum revenue
Map + combine:
cardinality of keys ≈ 5e6
after combine, shuffle payload ≈ 5e6 × 32 B × map_tasks_factor
suppose effective shuffle 80 GB
Cluster: 100 executors, 10 Gbps effective per host shared
shuffle wall ≈ max(network, disk spill)
80 GB across 100 hosts ≈ 0.8 GB/host ≈ seconds–minutes
if even; if 1 key has 20% of bytes → that reducer dominates
Join to 20 GB dimension (product catalog):
broadcast hash if 20 GB fits each executor memory budget
else hash-partition both on sku
SLA: finish by 05:00 with 02:00 start → 3 h budget
leave 30–40% headroom for stragglers and retriesTrade-offs to weigh
More parallelism versus shuffle overhead
Materialize versus recompute
Hash join versus sort-merge
Exact distinct versus sketches
Batch simplicity versus stream freshness
Choosing and explaining batch processing algorithms
A complete explanation connects the mechanism to a requirement. Describe what improves when you introduce it, which new failure cases become possible, and how you would know that the trade-off is worthwhile.
Explain the core idea
Batch jobs run finite datasets through staged algorithms—map, shuffle, reduce, joins, and external sort—with clear completion.
Shuffle and skew dominate cost; combiners and join strategy exist to cut or tame that cost.
Fault tolerance is materialize-or-recompute; stage wall time follows the slowest task at the barrier.
When it belongs
Designing ETL, warehouse transforms, offline features, or large aggregations.
Explaining MapReduce, Spark stages, or why a join blew up memory.
Contrasting batch with stream processing for latency versus simplicity.
How it fails
Key skew and stragglers that stretch barriers to many times the mean task time.
Unsafe combiners that produce wrong aggregates that look plausible.
Broadcast joins on oversized build sides and uncheckpointed deep lineages.
Trade-offs to weigh
Shuffle parallelism versus scheduling and file overhead.
Hash versus sort-merge joins under memory and sortedness constraints.
Materialization cost versus recomputation risk on failure.
Common misunderstandings
Assuming more executors fix a single hot reducer.
Confusing wide-column or streaming engines with batch stage physics.
Treating mean task time as the ETA when p99 task time owns the barrier.
Wrap-up
Batch processing algorithms are how finite data becomes correct aggregates on a deadline: partition, combine, shuffle, join, sort, and commit. Optimize shuffle bytes and skew first, pick join algorithms from memory reality, and decide deliberately where intermediates materialize. Use the stream processing note when the dataset never ends.
Primary sources
Key takeaway
Batch is finite-input stage physics: cut shuffle, tame skew, choose joins you can fit, and materialize what you cannot afford to recompute—so morning SLA meets correct totals.