Published on
·5 min read

MapReduce: Simplified Data Processing on Large Clusters

Authors

Original Paper

The Core Abstraction

MapReduce reduces distributed computation to two user-defined functions. The programmer writes a map function and a reduce function, and the framework handles everything else: parallelization, fault tolerance, data distribution, and load balancing.

The computation takes a set of input key/value pairs and produces a set of output key/value pairs:

map:(k1,v1)list(k2,v2)\text{map}: (k_1, v_1) \rightarrow \text{list}(k_2, v_2)
reduce:(k2,list(v2))list(v2)\text{reduce}: (k_2, \text{list}(v_2)) \rightarrow \text{list}(v_2)

The map function processes each input pair and emits intermediate key/value pairs. The framework groups all intermediate values with the same key and passes them to reduce, which merges these values to produce the final output.

Why This Matters

Before MapReduce, processing terabytes of data required writing custom code to handle distribution, failure recovery, and coordination. Most engineers spent more time on infrastructure than on the actual computation logic.

Consider counting word frequencies across billions of web pages. The map function emits (word, 1) for each word encountered. The reduce function sums all counts for each word:

map(String key, String value):
    // key: document name
    // value: document contents
    for each word w in value:
        EmitIntermediate(w, "1")

reduce(String key, Iterator values):
    // key: a word
    // values: a list of counts
    int result = 0
    for each v in values:
        result += ParseInt(v)
    Emit(AsString(result))

The framework automatically partitions the input into MM splits, spawns map tasks across available machines, and routes intermediate data to RR reduce tasks.

Execution Model

The master node maintains state for each map and reduce task: idle, in-progress, or completed. When a map task completes, it reports the locations of RR intermediate files (one per reduce partition) back to the master.

Data locality is critical for performance. The master schedules map tasks on machines that contain replicas of the input data, or at least on machines in the same network switch. At Google's scale in 2004, network bandwidth was a relatively scarce resource.

The number of reduce tasks is constrained by the desired number of output files. Users often set RR much smaller than MM because each reduce task produces one output file.

Fault Tolerance Through Re-execution

Worker failures are handled through simple re-execution. The master pings workers periodically. If no response arrives within a timeout, the master marks all map tasks completed by that worker as idle (needing re-execution) because their output was stored locally.

For map tasks, re-execution is required because intermediate output lives on the failed machine's local disk. Completed reduce tasks do not need re-execution because their output is stored in a global file system.

This design assumes that map and reduce functions are deterministic. Given the same input, they produce the same output. Non-deterministic functions complicate the semantics when tasks are re-executed.

Handling Stragglers

Near the end of a computation, the master schedules backup executions of in-progress tasks. Whichever execution finishes first wins. This optimization addresses the "straggler" problem where one slow machine delays the entire job.

In Google's experience, enabling backup tasks reduced job completion time by 44% for a particular sort benchmark. Stragglers can occur due to disk errors, competition from other jobs, or hardware issues that slow a machine without causing outright failure.

Combiners: Local Aggregation

For associative and commutative reduce functions, a combiner function can pre-aggregate data on the map side before network transfer. In the word count example, instead of sending millions of (the, 1) pairs across the network, the combiner produces (the, 12847) locally.

The combiner function has the same signature as the reduce function. Often they use identical code. The difference is that combiner output goes to intermediate files while reducer output goes to the final output.

Partition Function

By default, intermediate keys are partitioned using hash(key) mod R. Sometimes users need special partitioning. For URL processing, you might want all URLs from the same host to end up in the same output file:

partition(k)=hash(hostname(k))modR\text{partition}(k) = \text{hash}(\text{hostname}(k)) \mod R

Practical Impact

MapReduce became the foundation for Hadoop, which dominated big data processing for a decade. The abstraction proved that hiding distributed systems complexity behind simple functional interfaces was not just possible but practical at massive scale.

The paper reports that Google ran over 100,000 MapReduce jobs per day in 2004, processing more than 20 petabytes of data. Each job used an average of 400 machines.

Modern systems like Spark have largely replaced MapReduce by keeping intermediate data in memory rather than writing to disk. But the core insight remains: express computation as transformations on key/value pairs, let the framework handle distribution and fault tolerance.