2010-08-27 15 views
16

Tengo unos documentos de 25k (4 GB en raw json) de datos en los que quiero realizar algunas operaciones de javascript para hacerlo más accesible para mi consumidor de datos finales (R), y yo Me gustaría hacer una especie de "control de versión" de estos cambios agregando una nueva colección para cada cambio, pero no puedo entender cómo map/reduce sin el reduce. Quiero un mapeo de documentos uno a uno: empiezo con 25,356 documentos en collection_1, y quiero terminar con 25,356 documentos en collection_2.mongoDB map/reduce menos the reduce

puedo entrar ilegalmente en él con esto:

var reducer = function(key, value_array) { 
    return {key: value_array[0]} 
} 

Y luego llamar así:

db.flat_1.mapReduce(mapper, reducer, {keeptemp: true, out: 'flat_2'}) 

(Mi asignador sólo llamadas emiten una vez, con una cadena como primer argumento y el documento final como el segundo. Es una colección de esos segundos argumentos que realmente quiero).

Pero eso parece incómodo y no sé por qué incluso funciona, ya que mi llamada emit Los argumentos en mi mapeador no son equivalentes al argumento de retorno de mi reducer. Además, termino con un documento como

{ 
    "_id": "0xWH4T3V3R", 
    "value": { 
     "key": { 
      "finally": ["here"], 
      "thisIsWhatIWanted": ["Yes!"] 
     } 
    } 
} 

que parece innecesario.

Además, un cursor que realiza sus propias inserciones ni siquiera es una décima tan rápido como mapReduce. No conozco a MongoDB lo suficiente para compararlo, pero supongo que se trata de 50x más lento. ¿Hay alguna manera de ejecutar un cursor en paralelo? No me importa si los documentos en mi collection_2 están en un orden diferente al de collection_1.

+0

La razón funciona es porque su emiten y reductor de la llamada * * son lo mismo. Como usas el valor [0] como salida de tu reductor, tiene que ser exactamente el mismo porque no lo has cambiado (está pasando por tu reductor). – null

Respuesta

3

Pero eso parece torpe y no sé por qué aún funciona, ya que mis emit argumentos de llamada en mi asignador no son equivalentes a la devolución argumento de mi reducer.

Son equivalentes. La función reduce toma una matriz de T valores y debe devolver un valor único en el mismo formato T. El formato de T se define mediante la función de mapa. Su función de reducción simplemente devuelve el primer elemento en la matriz de valores, que siempre será del tipo T. Es por eso que funciona :)

Parece que va por buen camino. Hice algo de experimentación y parece que no se puede hacer un db.collection.save() desde la función de mapa, pero puede hacer esto desde la función reducir. Su función de mapa debe simplemente construir el formato de documento que necesita:

function map() { 
    emit(this._id, { _id: this.id, heading: this.title, body: this.content }); 
} 

La función de mapa reutiliza la ID del documento original. Esto debería evitar cualquier paso de reducción, ya que ningún valor compartirá la misma clave.

La función de reducción simplemente puede devolver null. Pero además, puede escribir el valor en una colección separada.

function reduce(key, values) { 
    db.result.save(values[0]); 

    return null; 
} 

Ahora db.result debe contener los documentos transformadas, sin ningún tipo de mapa a reducir el ruido adicional que tendría en la colección temporal. Realmente no he probado esto en grandes cantidades de datos, pero este enfoque debería aprovechar la ejecución paralelizada de las funciones map-reduce.

+2

De esta manera tomó 523s y terminó con una colección exactamente como yo lo quería, mientras que la manera hackish que describí en la pregunta toma 319s. Es lamentable que no pueda simplemente llamar a 'db.coll.mapReduce (myMapperFunc, null, {'out': 'output'})'. Creo que reduce es capaz de guardar por lotes/insertar un conjunto completo de elementos; Creo que el cuello de botella aquí es el 'save()' llamado en cada reducción. – chbrown

+1

@chbrown: Sí, el 'save()' se realiza dos veces para cada documento; el ahorro reducido estándar en la colección temporal y el guardado explícito en una colección separada. Simplemente curioso, ¿esta solución es realmente más rápida que usar un solo cursor? –

+0

Hola a todos, tenemos un problema similar para manejar grandes conjuntos de datos y dado que la concatenación de matrices y la ejecución de documentos grandes en reducción no funcionan, hemos seguido el ejemplo anterior de guardar el documento en una colección separada y devolver nulo desde reducir. Funciona bien, pero db se cuelga cuando hacemos cualquier otra operación mientras ejecutamos mapreduce. hay alguna mejor aplicación para el mismo. – MRK

6

Al utilizar map/reduce siempre va a terminar con

{ "value" : { <reduced data> } } 

el fin de eliminar la clave value que tendrá que utilizar una función finalize.

Aquí está la más simple que puede hacer para copiar datos de una colección a otra:

map = function() { emit(this._id, this); } 
reduce = function(key, values) { return values[0]; } 
finalize = function(key, value) { db.collection_2.insert(value); } 

Entonces, cuando usted funcionaría con normalidad:

db.collection_1.mapReduce(map, reduce, { finalize: finalize }); 
+4

"La función de finalización no debe acceder a la base de datos por ningún motivo". - [Documentos oficiales de MongoDB] (http://docs.mongodb.org/manual/reference/command/mapReduce/#mapreduce-finalize-cmd) – bloudermilk

+0

Verdadero ... pero no obstante útil para poder hacerlo. –

+0

¡Este es un cuello de botella de rendimiento completo y va en contra de Map-Reduce !!! Por favor, no hagas esto. –

0

que enfrentan la misma situación. Pude lograr esto a través de la consulta y proyección de Mongo. ver Mongo Query

1

Cuando tienes acceso a la consola mongo, acepta algunos comandos de Javascript y entonces es simple:

map = function(item){ 
     db.result.insert(item); 
} 

db.collection.find().forEach(map); 
Cuestiones relacionadas