← All weekly notes

6 min read

Reading MapReduce: making failure part of the runtime

How map, shuffle and reduce turn large batch jobs into schedulable, repeatable tasks with locality and failure recovery.

Distributed systems · MapReduce · Data processing · Fault tolerance

In September I read MapReduce: Simplified Data Processing on Large Clusters. The map and reduce functions are small. The important work is in the runtime that schedules thousands of tasks, moves intermediate data and treats machine failure as routine.

Paper: MapReduce: Simplified Data Processing on Large Clusters (PDF)

The programming model

A job supplies two functions:

map(k1, v1)    → list(k2, v2)reduce(k2, []) → list(v2)

Map transforms input records into intermediate key-value pairs. The runtime groups all values with the same intermediate key. Reduce processes each group and emits final output.

For word count:

func Map(_ string, document string) []Pair {	var out []Pair	for _, word := range strings.Fields(document) {		out = append(out, Pair{Key: word, Value: 1})	}	return out} func Reduce(word string, counts []int) Pair {	total := 0	for _, count := range counts {		total += count	}	return Pair{Key: word, Value: total}}

The user code does not choose machines, retry failures or transfer groups between workers. The framework owns those concerns.

Input splitting and map tasks

Large input files are divided into logical splits. The master creates one map task per split and multiple reduce tasks. Workers repeatedly ask the master for work.

The scheduler tries to place a map task on a machine that already stores the input block, or at least in the same network switch. This is data locality: move computation to data instead of sending large input across the network.

Locality matters because aggregate disk bandwidth can scale with the cluster, while cross-rack bandwidth is shared and more limited.

The shuffle is the real distributed boundary

Map workers buffer intermediate pairs in memory and periodically spill them to local disk. A partition function chooses the reduce task:

partition = hash(intermediateKey) % reduceCount

Within each partition, records are sorted by key. The master tells reduce workers where map outputs are located. Each reducer fetches its partition from every completed mapper, merges the sorted data and presents one key with its values to the reduce function.

The shuffle performs several jobs at once:

  • Network transfer from all mappers to reducers.
  • Partitioning by reduce owner.
  • Sorting by intermediate key.
  • Grouping equal keys.
  • Back-pressure through fetch and merge behaviour.

Many performance problems attributed to “MapReduce” are really shuffle problems: skewed keys, too much intermediate data, small records or saturated network paths.

Why map output is local but reduce output is durable

Map output is intermediate and can be regenerated by rerunning the map task. Writing it to the local worker disk is cheaper than replicating it in the distributed file system.

Final reduce output is written to the distributed file system because it is the job’s durable result.

This is a clean use of lineage. If data is cheap to recompute, replication may be unnecessary. The system spends durability where recomputation is no longer convenient.

Failure recovery by re-execution

The master pings workers. When a worker fails:

  • Its completed map tasks become idle because their local output disappeared.
  • In-progress map and reduce tasks become idle and are rescheduled.
  • Completed reduce tasks remain complete because their output is already durable.

Map and reduce functions should be deterministic for re-execution to produce the same logical output. The framework uses atomic rename when publishing task output, preventing partial attempts from becoming the visible result.

The system does not prevent failure. It makes work small and repeatable enough that failure is an ordinary scheduling event.

Stragglers and speculative execution

A job can be nearly complete while one slow task holds the result. The worker may have failing hardware, resource contention or an unusually expensive input partition.

Near the end of a job, the master can launch backup executions of remaining tasks. The first attempt to finish wins. This is speculative execution.

Speculation reduces tail latency when slowdowns are rare and unpredictable. It wastes resources when the task is slow because the input is intrinsically larger or because the whole cluster is overloaded. Modern schedulers therefore need careful straggler detection rather than blindly duplicating work.

Combiners reduce network volume

When reduce is associative and commutative, the map worker can apply a local combiner before sending data.

For word count, thousands of (word, 1) pairs from one mapper can become (word, localTotal). The final reducer then combines a much smaller set of partial counts.

A combiner is an optimization, not a guaranteed phase. Correctness must not depend on how many times it runs. Average is a classic trap: averaging local averages is wrong unless the intermediate value carries both sum and count.

Partitioning and skew

Hash partitioning balances many ordinary keys, but a single hot key still goes to one reducer. A large group can dominate the job’s completion time.

Possible responses include:

  • Add a random sub-key and run a second aggregation stage.
  • Sample input and build range boundaries.
  • Separate known heavy keys.
  • Increase map-side aggregation.
  • Choose more reduce partitions, within scheduling and file-count limits.

The framework hides distribution mechanics, but it cannot remove the data’s distribution.

Useful refinements from the paper

The implementation includes practical hooks:

  • Custom partition functions for domain-specific grouping.
  • Ordering guarantees within a reduce partition.
  • Side inputs distributed to workers.
  • Counters for observing data quality and progress.
  • Skipping known bad records when deterministic user code crashes.
  • Local execution for testing and debugging.

These are reminders that a programming model succeeds when its operational tools are as usable as its core abstraction.

Limits of the model

MapReduce is naturally batch-oriented. Multi-stage pipelines repeatedly materialize data, increasing latency and I/O. Iterative algorithms may reread the same state many times. Interactive queries and unbounded streams need different execution models.

The abstraction is still influential because it isolates a reusable pattern:

split independent work→ move data by key→ process each key in one place→ retry deterministic tasks

What I keep from the paper

MapReduce’s contribution is not that map and reduce exist. It is that a narrow functional boundary gives the runtime enough control to parallelize, schedule near data, recover by replay and mitigate stragglers.

When designing distributed work today, I use the same questions:

  • Can work be divided into deterministic tasks?
  • Which intermediate data is cheaper to recompute than replicate?
  • Where is the expensive shuffle boundary?
  • How will skew appear at the tail?
  • Can retries publish output idempotently?

Those questions apply well beyond MapReduce—to worker pools, build systems, stream processors and background job platforms.