2011-06-10 25 views
8

Tengo una colección MongoDB, cuyos documentos utilizan varios niveles de anidación, de la que me gustaría extraer una matriz multidimensional compilada a partir de un subconjunto de sus campos. Tengo una solución que me funciona en este momento, pero quiero comprender mejor este concepto de "idempotencia" y sus consecuencias relacionadas con la función de reducción.MongoDB Map/Reduce Array agregación pregunta

{ 
    "host_name" : "gateway", 
    "service_description" : "PING", 
    "last_update" : 1305777787, 
    "performance_object" : [ 
    [ "rta", 0.105, "ms", 100, 500, 0 ], 
    [ "pl", 0, "%", 20, 60, 0 ] 
    ] 
} 

Y aquí el mapa/reducir las funciones

var M = function() { 
    var hn = this.host_name, 
     sv = this.service_description, 
     ts = this.last_update; 
    this.performance_object.forEach(function(P){ 
    emit({ 
     host: hn, 
     service: sv, 
     metric: P[0] 
    }, { 
     time: ts, 
     value: P[1] 
    }); 
    }); 
} 
var R = function(key,values) { 
    var result = { 
    time: [], 
    value: [] 
    }; 
    values.forEach(function(V){ 
    result.time.push(V.time); 
    result.value.push(V.value); 
    }); 
    return result; 
} 
db.runCommand({ 
    mapreduce: <colname>, 
    out: <col2name>, 
    map: M, 
    reduce: R 
}); 

de datos se devuelve en una estructura útil, que volver a formatear/género con finalizar para la representación gráfica.

{ 
    "_id" : { 
    "host" : "localhost", 
    "service" : "Disk Space", 
    "metric" : "/var/bck" 
    }, 
    "value" : { 
    "time" : [ 
     [ 1306719302, 1306719601, 1306719903, ... ], 
     [ 1306736404, 1306736703, 1306737002, ... ], 
     [ 1306766401, 1306766701, 1306767001, ... ] 
    ], 
    "value" : [ 
     [ 122, 23423, 25654, ... ], 
     [ 336114, 342511, 349067, ... ], 
     [ 551196, 551196, 551196, ... ] 
    ] 
    } 
} 

Finalmente ...

[ [1306719302,122], [1306719601,23423], [1306719903,25654], ... ] 

TL; DR: ¿Cuál es el comportamiento esperado con el oberved "fragmentación" de los resultados de la matriz?

Entiendo que la función de reducción se puede llamar varias veces en matriz (es) de valores emitidos, por lo que hay varios "fragmentos" de las matrices completas, en lugar de una única matriz. Los fragmentos de matriz son generalmente de 25-50 elementos y es bastante fácil limpiar esto en finalize(). Concat() las matrices, las intercalas como [tiempo, valor] y ordenan. Pero lo que realmente quiero saber es si esto puede volverse más complejo:

1) ¿Se observa la fragmentación debido a mi código, a la implementación de MongoDB o al algoritmo Map/Reduce?

2) ¿Alguna vez habrá un agrupamiento más profundo (recursivo) de fragmentos de matriz en configuraciones fragmentadas o incluso solo por mi apresurada implementación? Esto rompería el método concat().

3) ¿Existe simplemente una mejor estrategia para obtener resultados de matriz como se muestra arriba?

EDIT: Modificado para emitir matrices:

Me tomó consejo Thomas y re-escribió para emitir matrices. No tiene ningún sentido dividir los valores.

var M = function() { 
    var hn = this.host_name, 
     sv = this.service_description, 
     ts = this.last_update; 
    this.performance_object.forEach(function(P){ 
    emit({ 
     host: hn, 
     service: sv, 
     metric: P[0] 
    }, { 
     value: [ ts, P[1] ] 
    }); 
    }); 
} 
var R = function(key,values) { 
    var result = { 
    value: [] 
    }; 
    values.forEach(function(V){ 
    result.value.push(V.value); 
    }); 
    return result; 
} 
db.runCommand({ 
    mapreduce: <colname>, 
    out: <col2name>, 
    map: M, 
    reduce: R 
}); 

Ahora el resultado es similar a esto:

{ 
    "_id" : { 
    "host" : "localhost", 
    "service" : "Disk Space", 
    "metric" : "/var/bck" 
    }, 
    "value" : { 
    "value" : [ 
     [ [1306736404,336114],[1306736703,342511],[1306737002,349067], ... ], 
     [ [1306766401,551196],[1306766701,551196],[1306767001,551196], ... ], 
     [ [1306719302,122],[1306719601,122],[1306719903,122], ... ] 
    ] 
    } 
} 

Y utiliza esta función de finalización para concatenar los trozos de matriz y ordenarlos.

... 
var F = function(key,values) { 
    return (Array.concat.apply([],values.value)).sort(function(a,b){ 
    if (a[0] < b[0]) return -1; 
    if (a[0] > b[0]) return 1; 
    return 0; 
    }); 
} 
db.runCommand({ 
    mapreduce: <colname>, 
    out: <col2name>, 
    map: M, 
    reduce: R, 
    finalize: F 
}); 

¿Cuál funciona muy bien:

{ 
    "_id" : { 
    "host" : "localhost", 
    "service" : "Disk Space", 
    "metric" : "/mnt/bck" 
    }, 
    "value" : [ [1306719302,122],[1306719601,122],[1306719903,122],, ... ] 
} 

Creo que la única pregunta que me corroe es si este Array.concat.apply ([], values.value) se puede confiar para limpiar el salida de reducir todo el tiempo.

Última edición: Mucho más simple ...

que han modificado la estructura del documento ya que el original ejemplo dado anteriormente, pero esto sólo cambia el ejemplo haciendo que la función de mapa muy simple.

Todavía estoy tratando de entender por qué Array.prototype.push.apply (resultado, V.data) funciona de manera diferente de result.push (V.data) ... pero funciona.

var M = function() { 
    emit({ 
    host: this.host, 
    service: this.service, 
    metric: this.metric 
    } , { 
    data: [ [ this.timestamp, this.data ] ] 
    }); 
} 
var R = function(key,values) { 
    var result = []; 
    values.forEach(function(V){ 
    Array.prototype.push.apply(result, V.data); 
    }); 
    return { data: result }; 
} 
var F = function(key,values) { 
    return values.data.sort(function(a,b){ 
    return (a[0]<b[0]) ? -1 : (a[0]>b[0]) ? 1 : 0; 
    }); 
} 

Tiene la misma salida que se muestra justo encima del encabezado LAST EDIT.

Gracias, Thomas!

Respuesta

3
  1. La "fragmentación" proviene de su código: reducir el parámetro de los valores de función puede contener {time:<timestamp>,value:<value>} emitida de su función de mapa, o {time:[<timestamps>],value:[<values]} de regresar de una llamada previa a su función de reducir.

  2. No sé si sucederá en la práctica, pero puede suceder en teoría.

  3. simplemente tienen su función de mapa emiten el mismo tipo de objetos que su reducir devuelve la función, es decir emit(<id>, {time: [ts], value: [P[1]]}), y cambiar su función de reducir en consecuencia, es decir Array.push.apply(result.time, V.time) y lo mismo para result.value.

    Bueno, yo realmente no entiendo por qué no se está usando un array de pares tiempo/valor, en lugar de un par de matrices, es decir emit(<id>, { pairs: [ {time: ts, value: P[1] ] }) o emit(<id>, { pairs: [ [ts, P[1]] ] }) en la función de mapa, y Array.push.apply(result.pairs, V.pairs) en la función de reducir. De esta forma, ni siquiera necesitará la función de finalización (excepto tal vez para "desenvolver" la matriz de la propiedad pares: porque la función reducir no puede devolver una matriz, debe envolverla de ese modo en un objeto)

+0

Gracias por la respuesta, la emisión de matrices es óptima. He actualizado el tema con el nuevo código y ahora solo quiero averiguar si reducir me arrojará algún conjunto anidado más profundo. – jcampbelly

+0

Ha cometido exactamente el mismo error que antes: lo que emite en su función de mapa es diferente de lo que devuelve en su función de reducción: uno contiene un "par" mientras que el otro tiene una matriz de pares. Quédese con el mismo "esquema" exacto y no tendrá ningún problema ni matriz anidada. –

+0

daré a este un tiro tan pronto como tengo la oportunidad: emiten ({ anfitrión: hn, servicio: sv, métrica: P [0] }, { valor: [[ts, P [ 1]]] }); – jcampbelly

Cuestiones relacionadas