Map Reduce in MongoDb


I had a problem where I needed to aggregate some documents in MongoDb. In Be My Eyes, we keep records of which version of ios the app is installed on. We do this to know which versions to support.
The natural solution would be to use the aggregation framework, however I had to do some string manipulation, since we only care about major versions at the moment.
So I went ahead and learned me som map - reduce. Map - reduce is a pretty simple concept, where you in the map step extract some data, and in the reduce step do some kind of manipulation of that data, to get some kind of values out of it.
The code I endend up with was:

db.iphone_distributions.remove({})

db.devices.mapReduce(
function() {
    var iosVersion = this.system_version.substring(10, 11);
    emit(this.model + iosVersion, {
        count: 1
    });
},
function(key, values) {
    var count = 0;

    values.forEach(function(v) {
        count += v['count'];;
    });

    return {
        count: count
    };
}, {
    query: {},
    out: "iphone_distributions"
}
)

The first function is the map function, where I extract the model (iPad, iPhone or iPod) and concat the major version number.
The emit function is a MongoDb function, where the first argument is an id, which groups the values, and the second argument is the stuff you want to work on in the reduce function.

The second function is the reduce function.
One thing that took me some time to understand is that the reduce function can be called more than once for the same group. Hence the return value of reduce, has to be the same as the second argument to emit in the map function.

The last argument to mapReduce is an object literal that can include many options.
The query option could have been left out, but I choose to keep it in - it is used to do a filter before the map function.
The out option tells mongo which collection to put the result in.

Image courtesey of https://www.flickr.com/photos/sukiweb/