Cómo mejorar el rendimiento de procesos MapReduce en Mongo

En este blog de Antoine Girbal (el creador de UMongo) podéis encontrar muchísima información práctica de cómo trabajar con Mongo, desde diseño de coleciones hasta temas de rendimiento.

En este caso me permito resumir uno de sus posts: How to speed up MongoDB Map Reduce by 20x

Para el ejercicio Antoine inserta 10 millones de documentos muy sencillos (con un valor aleatorio entre 0 y 1 millón.

> for (var i = 0; i < 10000000; ++i){ db.uniques.insert({ dim0: Math.floor(Math.random()*1000000) });}
> db.uniques.findOne()
{ "_id" : ObjectId("51d3c386acd412e22c188dec"), "dim0" : 570859 }
> db.uniques.ensureIndex({dim0: 1})
> db.uniques.stats()
{
 "ns" : "test.uniques",
 "count" : 10000000,
 "size" : 360000052,
 "avgObjSize" : 36.0000052,
 "storageSize" : 582864896,
 "numExtents" : 18,
 "nindexes" : 2,
 "lastExtentSize" : 153874432,
 "paddingFactor" : 1,
 "systemFlags" : 1,
 "userFlags" : 0,
 "totalIndexSize" : 576040080,
 "indexSizes" : {
 "_id_" : 324456384,
 "dim0_1" : 251583696
 },
 "ok" : 1
}

Nuestro proceso MapReduce se encargará de contar el número de valores únicos (como media tendremos 10 documentos con el mismo valor :)).

El proceso MapReduce quedaría así:

> db.runCommand(
{ mapreduce: "uniques", 
map: function () { emit(this.dim0, 1); }, 
reduce: function (key, values) { return Array.sum(values); }, 
out: "mrout" })
{
 "result" : "mrout",
 "timeMillis" : 1161960,
 "counts" : {
 "input" : 10000000,
 "emit" : 10000000,
 "reduce" : 1059138,
 "output" : 999961
 },
 "ok" : 1
}

En el ejemplo la salida tarda 1200 segundos en procesarse: hay 10 millones de maps, 1 millón de reduces y 9999961 documentos en la salida:

> db.mrout.find()
{ "_id" : 1, "value" : 10 }
{ "_id" : 2, "value" : 5 }
{ "_id" : 3, "value" : 6 }
{ "_id" : 4, "value" : 10 }
{ "_id" : 5, "value" : 9 }
{ "_id" : 6, "value" : 12 }
{ "_id" : 7, "value" : 5 }
{ "_id" : 8, "value" : 16 }
{ "_id" : 9, "value" : 10 }
{ "_id" : 10, "value" : 13 }
...

La primera optimización que hacen en el post y la que vamos a ver hoy es usar un sort en el map.

Cuando procesamos la entrada sin ordenar obtenemos los valores en orden aleatorio y no se pueden reducir todos en memoria teniendo que escribir a disco a una colección temporal, leerlos posteriormente y reducirlos.

Si incluimos un order:

> db.runCommand(
{ mapreduce: "uniques", 
map: function () { emit(this.dim0, 1); }, 
reduce: function (key, values) { return Array.sum(values); }, 
out: "mrout", 
sort: {dim0: 1} })
{
 "result" : "mrout",
 "timeMillis" : 192589,
 "counts" : {
 "input" : 10000000,
 "emit" : 10000000,
 "reduce" : 1000372,
 "output" : 999961
 },
 "ok" : 1
}

En el ejemplo se obtiene una mejora de x6 veces al poder realizarse los reduces en RAM.

Deja un comentario