Once a distribted file system is in place, in this case GFS, we now want to execute some queries! This is where Google’s mapreduce framework comes in. As you will see, it fits very nicely around the GFS
Functional Programming, Map, and Reduce
The original concept of mapreduce has its roots in functional programming. Basically, a map will apply a function to a set of input data, transform this data in some way, and emit the trnasformed data. A reduce will apply a function to a set of input data, and essentially combine that input data in some way so that it returns a smaller set of output data. Lets see an example:
Example
The following python program contains a map function that applies a function to each element of a list that will increment that element. The reduce function then sums the elements.
>>> nums=[1,2,3,4,5]
>>> newNums = map( lambda x:x+1, nums )
>>> print newNums
[2, 3, 4, 5, 6]
>>> sum = reduce( lambda x,y: x+y, newNums )
>>> print sum
20
Similarly, google mapreduce works by defining a map function and a reduce function. The map function will take objects stored in the GFS, and for each object, willeither emit a key/value pair, or nothing. The reducer then will receive a key
and an array of values that were all mapped to key
. The reducer then performs user defined actions and returns a list of values, again associated with key
. In this way, reducers can be called in parallel.
Recall that one of the advantages of functional languages is that parallelism can be identified rather easily. Similarly, maps can be performed in parallel, and reduces can be performed in parallel as they aggregate final values. In fact, there is no user defined synchronozation, meaning that the computation is as parallel as there are data objects to operate on, and processors. This is known as an embarrassingly parallel problem, a bad name for a good problem.
Example
Map and reduce functions have the following signatures:


So, constucting mapreduce queries is easy for the programmer, just make 2 functions and don’t worry about parallelism or synchronization.
Some example applications from the paper:
Example
Count words in bunch of documents
quote from paper:
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));
other examples. quote from paper:
Distributed Grep: The map function emits a line if it matches a supplied pattern. The reduce function is an identity function that just copies the supplied intermedi- ate data to the output.
Count of URL Access Frequency: The map func- tion processes logs of web page requests and outputs [URL, 1]. The reduce function adds together all values for the same URL and emits a [URL, total count] pair.
[target, list(source)]
Term-Vector per Host: A term vector summarizes the most important words that occur in a document or a set of documents as a list of [word, f requency] pairs. The map function emits a [hostname, term vector] pair for each input document (where the hostname is extracted from the URL of the document). The re- duce function is passed all per-document term vectors for a given host. It adds these term vectors together, throwing away infrequent terms, and then emits a final [hostname, term vector] pair.
Inverted Index: The map function parses each docu- ment, and emits a sequence of [word, document ID] pairs. The reduce function accepts all pairs for a given word, sorts the corresponding document IDs and emits a [word,list(document ID)] pair.Thesetofalloutput pairs forms a simple inverted index. It is easy to augment this computation to keep track of word positions.
Distributed Sort: The map function extracts the key from each record, and emits a [key, record] pair. The reduce function emits all pairs unchanged. This compu- tation depends on the partitioning facilities described in Section 4.1 and the ordering properties described in Sec- tion 4.2.
Knowing how GFS works, the architectural diagram of a MapReduce query says it all. From the paper:
Workers read chunks, apply the maps. The worker gets the chunk information from the GFS master node and accesses the appropriate chunknodes. The results of maps are writted to intermediate files (usually locally, but wherever the master node says there is room). The reducers then start reducing, producing intermediate files along the way. At the end, output files are created, that can be distributed across many nodes! (remember, a file’s chunks can be anywhere in the GFS system). So, input data can be huge! (scalable to GFS limitations, which are super high), and output can even be huge!. Also, since the output is also files, the output can be used as input to further MapReduce jobs!
Of course, the system tries to keep data local and take advantage of rack infrastructure. Reducers are first run on a node, then nodes in the same rack are reduced (they share a router), then adjacent racks are reduced (to decrease router trafic), and so on.
Some examples (from the paper):