MongoDB Map Reduce

Map-Reduce es un modelo de computación que, en términos simples, consiste en descomponer (MAP) grandes volúmenes de trabajo (datos) para ejecutarlos y luego fusionar los resultados en el resultado final (REDUCE).

El Map-Reduce proporcionado por MongoDB es muy flexible y también bastante práctico para el análisis de datos a gran escala.

Comando MapReduce

La siguiente es la sintaxis básica de MapReduce:

>db.collection.mapReduce(
   function() {emit(key,value);},  //map 函数
   function(key,values) {return reduceFunction},   //reduce 函数
   {
      out: collection,
      query: document,
      sort: document,
      limit: number
   }
)

Para usar MapReduce es necesario implementar dos funciones: la función Map y la función Reduce. La función Map llama a emit(key, value), recorre todos los registros de la collection y pasa key y value a la función Reduce para que los procese.

La función Map debe llamar a emit(key, value) para devolver pares clave-valor.

Descripción de parámetros:

  • map: función de mapeo (genera una secuencia de pares clave-valor, que se utiliza como parámetro de la función reduce).
  • reduce: función de reducción (estadística). La tarea de la función reduce es convertir key-values en key-value, es decir, transformar el array de values en un único valor value.
  • out: colección donde se almacenan los resultados estadísticos (si no se especifica, se usa una colección temporal que se elimina automáticamente cuando el cliente se desconecta).
  • query: una condición de filtro; solo los documentos que cumplan la condición invocarán la función map. (query, limit y sort se pueden combinar libremente).
  • sort: parámetro de ordenación sort que se combina con limit (también ordena los documentos antes de enviarlos a la función map), puede optimizar el mecanismo de agrupación.
  • limit: límite superior de la cantidad de documentos que se envían a la función map (si no hay limit, usar sort solo no es de mucha utilidad).

El siguiente ejemplo busca datos con status:"A" en la colección orders, los agrupa por cust_id y calcula la suma de amount.


Usar MapReduce

Considere la siguiente estructura de documento para almacenar los artículos de los usuarios. El documento almacena el user_name del usuario y el campo status del artículo:

>db.posts.insert({
   "post_text": "Ejemplo,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "Ejemplo,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "Ejemplo,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "Ejemplo,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "Ejemplo,最全的技术文档。",
   "user_name": "mark",
   "status":"disabled"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "Ejemplo,最全的技术文档。",
   "user_name": "ejemplo",
   "status":"disabled"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "Ejemplo,最全的技术文档。",
   "user_name": "ejemplo",
   "status":"disabled"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "Ejemplo,最全的技术文档。",
   "user_name": "ejemplo",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })

Ahora, usaremos la función mapReduce en la colección posts para seleccionar los artículos publicados (status:"active"), agruparlos por user_name y calcular la cantidad de artículos de cada usuario:

>db.posts.mapReduce( 
   function() { emit(this.user_name,1); }, 
   function(key, values) {return Array.sum(values)}, 
      {  
         query:{status:"active"},  
         out:"post_total" 
      }
)

El resultado de la salida de mapReduce anterior es:

{
        "result" : "post_total",
        "timeMillis" : 23,
        "counts" : {
                "input" : 5,
                "emit" : 5,
                "reduce" : 1,
                "output" : 2
        },
        "ok" : 1
}

El resultado indica que hay 5 documentos que cumplen la condición de consulta (status:"active"). En la función map se generaron 5 documentos de pares clave-valor, y finalmente, con la función reduce, las mismas claves se agruparon en 2 grupos.

Descripción de parámetros específicos:

  • result: el nombre de la collection que almacena los resultados. Es una colección temporal que se elimina automáticamente cuando se cierra la conexión de MapReduce.
  • timeMillis: tiempo de ejecución, en milisegundos.
  • input: cantidad de documentos que cumplen la condición y que se envían a la función map.
  • emit: número de veces que se llama a emit en la función map, es decir, la cantidad total de datos en todas las colecciones.
  • output: cantidad de documentos en la colección de resultados.(count es muy útil para la depuración).
  • ok: indica si se realizó correctamente; 1 para éxito.
  • err: si falla, aquí puede estar el motivo del fallo, aunque por experiencia, el motivo es bastante ambiguo y de poca utilidad.

Use el operador find para ver los resultados de la consulta de mapReduce:

> var map=function() { emit(this.user_name,1); }
> var reduce=function(key, values) {return Array.sum(values)}
> var options={query:{status:"active"},out:"post_total"}
> db.posts.mapReduce(map,reduce,options)
{ "result" : "post_total", "ok" : 1 }
> db.post_total.find();

La consulta anterior muestra los siguientes resultados:

{ "_id" : "mark", "value" : 4 }
{ "_id" : "ejemplo", "value" : 1 }

De manera similar, MapReduce se puede utilizar para construir consultas de agregación grandes y complejas.

Las funciones Map y Reduce se pueden implementar con JavaScript, lo que hace que el uso de MapReduce sea muy flexible y potente.

Otras extensiones