2012-04-16 5 views
5

(1) I tienen las siguientes añadido a una colección:

{ "_id" : 1, "hitsPerOneSecond" : [ 2, 3, 5, 4, 1, 2, 3, 4, 1, 2 ], "startTime" : ISODate("2012-04-07T10:41:33.380Z"), "returnCodeHits" : { "300" : 5, "200" : 12 }, "xxxServer" : "xxx:8100", "statsSummarizedToSeconds" : 10, "pathStats_xxx_api_get_version" : [ 0.2280779683225852, 0.030849283020361273, 0.9947690473370484 ], "pathStats_xxx_api_get_response" : [ 1.2163705612407407, 1.0602539963494662, 1.4853219936411421 ], "type" : "xxxType", "startTimeStr" : "07-04-2012:10AM" } 

{ "_id" : 2, "hitsPerOneSecond" : [ 2, 3, 5, 4, 1, 2, 3, 4, 1, 2 ], "startTime" : ISODate("2012-04-07T10:41:43.380Z"), "returnCodeHits" : { "300" : 5, "200" : 12 }, "xxxServer" : "xxx:8100", "statsSummarizedToSeconds" : 10, "pathStats_xxx_api_get_version" : [ 0.2280779683225852, 0.030849283020361273, 0.9947690473370484 ], "pathStats_xxx_api_get_response" : [ 1.2163705612407407, 1.0602539963494662, 1.4853219936411421 ], "type" : "xxxType", "startTimeStr" : "07-04-2012:10AM" } 

(2) Cuando se realiza la siguiente agregación:

db.newStats.aggregate({$unwind: "$hitsPerOneSecond"},{$group:{_id:"$startTimeStr", totalHits: {$sum: "$hitsPerOneSecond"}, totalHitsCount: {$sum: 1}, avgHit: {$avg: "$hitsPerOneSecond"}, minHit: {$min:"$hitsPerOneSecond"}, maxHit:{$max: "$hitsPerOneSecond"}}}); 

(3) resultados sale correctamente:

{ 
"result" : [ 
    { 
     "_id" : "07-04-2012:10AM", 
     "totalHits" : 54, 
     "totalHitsCount" : 20, 
     "avgHit" : 2.7, 
     "minHit" : 1, 
     "maxHit" : 5 
    } 
], 
"ok" : 1 

}

(4) Sin embargo, necesito realizar un desenrollado en 'pathStats_xxx_api_get_response' (de la colección) en la misma agregación anterior para poder tener totalResponses, totalResponsesCount, avgResponse, minResponse y maxResponse de salida en el mismo resultado anterior. Por lo tanto, mi resultado debería ser algo como esto:

{ 
"result" : [ 
    { 
     "_id" : "07-04-2012:10AM", 
     "totalHits" : 54, 
     "totalHitsCount" : 20, 
     "avgHit" : 2.7, 
     "minHit" : 1, 
     "maxHit" : 5, 
        "totalResponses" : ?? 
        "totalResponsesCount": ?? 
     "avgResponse" : 2.7, 
     "minResponse" : 1, 
     "maxResponse" : 5 
    } 
], 
"ok" : 1 

}

No estoy seguro exactamente cómo añadir más $ descansar en el mismo agregación como soy casi allí!

Respuesta

2

Posiblemente la solución más fácil sea hacer esto con dos operaciones de agregación separadas y combinar los resultados en su aplicación.

Como alternativa, puede hacer esto con un mapa Reducir el funcionamiento:

El siguiente mapa y reducir funciones deben proporcionar los resultados que usted está buscando:

var map = function() { 
    var totalHits = this.hitsPerOneSecond.map(function(a,b){return a+b;}); 
    var totalHitsCount = this.hitsPerOneSecond.length; 
    var avgHit = totalHits/totalHitsCount; 
    var minHit = Math.min.apply(Math, this.hitsPerOneSecond); 
    var maxHit = Math.max.apply(Math, this.hitsPerOneSecond); 
    var totalResponses = pathStats_xxx_api_get_response.map(function(a,b){return a+b;}); 
    var totalResponsesCount = this.pathStats_xxx_api_get_response.length; 
    var avgResponse = totalResponses/totalResponsesCount; 
    var minResponse = Math.min.apply(Math, this.pathStats_xxx_api_get_response); 
    var maxResponse = Math.max.apply(Math, this.pathStats_xxx_api_get_response); 
    emit(this.startTimeStr, { 
    "totalHits": totalHits, 
    "totalHitsCount": totalHitsCount, 
    "avgHit": avgHit, 
    "minHit": minHit, 
    "maxHit": maxHit, 
    "totalResponses": totalResponses, 
    "totalResponsesCount": totalResponsesCount, 
    "avgResponse": avgResponse, 
    "maxResponse": maxResponse, 
    "minResponse": minResponse 
    }) 
} 

var reduce = function(key, values) { 
    var output = { 
    "totalHits": 0, 
    "totalHitsCount": 0, 
    "avgHit": 0, 
    "minHit": null, 
    "maxHit": null, 
    "totalResponses": 0, 
    "totalResponsesCount": 0, 
    "avgResponse": 0, 
    "maxResponse": null, 
    "minResponse": null 
    }; 
    values.forEach(function(v) { 
    output.totalHits += v.totalHits; 
    output.totalHitsCount += v.totalHitsCount; 
    output.avgHit = output.totalHits/output.totalHitsCount; 
    if (output.minHit == null) { 
     output.minHit = v.minHit; 
    } else { 
     if (v.minHit < output.minHit) { 
     output.minHit = v.minHit 
     } 
    } 
    if (output.maxHit == null) { 
     output.maxHit = v.maxHit; 
    } else { 
     if (v.maxHit > output.maxHit) { 
     output.maxHit = v.maxHit 
     } 
    } 

    output.totalResponses += v.totalResponses; 
    output.totalResponsesCount += v.totalResponsesCount; 
    output.avgResponse = output.totalResponses/output.totalResponsesCount; 
    if (output.minResponse == null) { 
     output.minResponse = v.minResponse; 
    } else { 
     if (v.minResponse < output.minResponse) { 
     output.minResponse = v.minResponse 
     } 
    } 
    if (output.maxResponse == null) { 
     output.maxResponse = v.maxResponse; 
    } else { 
     if (v.maxResponse > output.maxResponse) { 
     output.maxResponse = v.maxResponse 
     } 
    } 
    }); 
    return output; 
} 

> db.newStats.mapReduce(map, reduce, {out:{inline:1}}) 
{ 
    "results" : [ 
     { 
      "_id" : "07-04-2012:10AM", 
      "value" : { 
       "totalHits" : 54, 
       "totalHitsCount" : 20, 
       "avgHit" : 2.7, 
       "minHit" : 1, 
       "maxHit" : 5, 
       "totalResponses" : 7.523893102462698, 
       "totalResponsesCount" : 6, 
       "avgResponse" : 1.253982183743783, 
       "maxResponse" : 1.4853219936411421, 
       "minResponse" : 1.0602539963494662 
      } 
     } 
    ], 
    "timeMillis" : 0, 
    "counts" : { 
     "input" : 2, 
     "emit" : 2, 
     "reduce" : 1, 
     "output" : 1 
    }, 
    "ok" : 1, 
} 
> 

Si no está familiarizado con MapReduce, la documentación se puede encontrar aquí: http://www.mongodb.org/display/DOCS/MapReduce

Además, hay algunos buenos ejemplos MapReduce en el libro de cocina de MongoDB: http://cookbook.mongodb.org/

La sección "Extras" del artículo del libro de recetas "Encontrar valores máximos y mínimos con documentos versionados" http://cookbook.mongodb.org/patterns/finding_max_and_min/ contiene un buen tutorial paso a paso de una operación Map Reduce, que explica cómo se ejecutan las funciones.

Esperamos que esto lo ayude a lograr los resultados deseados. Si puede encontrar una manera de hacerlo con una única operación de agregación, comparta su solución para que la Comunidad pueda obtener el beneficio de su experiencia. Gracias.

Estas son algunas notas sobre MapReduce, en respuesta a su comentario:

MapReduce ejecuta JavaScript en el servidor. Como resultado, puede encontrar que el rendimiento sufre otras operaciones. Map Reduce es bueno para las operaciones de una vez en una vez que pueden realizarse en un momento en que el servidor no está en su pico de tráfico. Es posible que el uso de Map Reduce para las estadísticas sobre la marcha de una gran colección no sea óptimo.

El marco de agregación, por otro lado, se basa en código nativo y no ejecuta JavaScript del lado del servidor, por lo que es más rápido que Map Reduce.

Si es posible, la mejor opción es agregar campos a cada documento que se pueda consultar.Esto agrega un poco de sobrecarga adicional a cada inserción o actualización, pero los resultados se devolverán mucho más rápidamente si se puede evitar una operación de reducción de mapa. Desafortunadamente, esto es difícil con valores máximos y mínimos y promedios.

Si una operación de Reducción de mapa es la única opción, hay algunas cosas que se pueden hacer para mitigar su impacto en el servidor. En primer lugar, es posible ejecutar un Map Reduce en un secundario con SlaveOk. Sin embargo, como los datos no se pueden escribir en un secundario, la salida debe devolverse en línea y, por lo tanto, está limitada a 16 MB. Algunos usuarios eliminarán un secundario del conjunto de réplicas, lo reiniciarán como un proceso de mongod independiente, ejecutarán la operación de reducción de mapa en él, copiarán la colección de salida donde sea que deba ir y volverán a unirse al conjunto de repica secundario.

Una última cosa a considerar es incrementales MapReduce: http://www.mongodb.org/display/DOCS/MapReduce#MapReduce-IncrementalMapreduce se puede pasar una consulta al mapa reducir el comando que sólo igualará documentos que han sido modificados desde la última MapReduce, y ejecutar el mapa reducir la operación con el reducir la opción de salida.

Esperemos que lo anterior le dará algunos elementos de reflexión sobre la mejor forma de calcular sus estadísticas. Incluir la información deseada en los documentos es preferible, pero si eso no es posible, usar el Marco de Agregación será más eficiente que Map Reduce.

Aquí es una nota en el Marco agregación y pymongo, en respuesta a la segunda comentario:

El marco de agregación se puede utilizar en pymongo con el método de comando del objeto base de datos.
La documentación sobre el método de comando se pueden encontrar aquí: http://api.mongodb.org/python/current/api/pymongo/database.html#pymongo.database.Database.command

para realizar una operación de agregación, pase un documento para el método de comando con dos llaves; "agregado" y "canalización". El valor de "agregado" es el nombre de la colección en la que se realizará la operación, y el valor de "canalización" será una matriz de las operaciones de agregación que se realizarán. Las tuberías se explican en la documentación "Agregación Marco": http://www.mongodb.org/display/DOCS/Aggregation+Framework#AggregationFramework-Pipelines

Aquí es un ejemplo de cómo se puede realizar la operación de desenrollado $ en pymongo:

In [1]: import pymongo 

In [2]: conn = pymongo.Connection() 

In [3]: db = conn.test 

In [4]: result = db.command({"aggregate":"newStats", "pipeline": 
          [{"$unwind": "$hitsPerOneSecond"}, 
          {"$group": {"_id":"$startTimeStr", 
              "totalHits": {"$sum": 
              "$hitsPerOneSecond"}, 
           "totalHitsCount": {"$sum": 1}, 
           "avgHit": {"$avg": "$hitsPerOneSecond"}, 
           "minHit": {"$min":"$hitsPerOneSecond"}, 
           "maxHit":{"$max": "$hitsPerOneSecond"}}}]}) 

In [5]: result 
Out[5]: 
{u'ok': 1.0, 
u'result': [{u'_id': u'07-04-2012:10AM', 
    u'avgHit': 2.7, 
    u'maxHit': 5.0, 
    u'minHit': 1.0, 
    u'totalHits': 54.0, 
    u'totalHitsCount': 20}]} 
+0

Este es un ejemplo fantástico. Muchas gracias por eso. Además, los enlaces a los documentos son muy apreciados.En términos de agregación, el objetivo era agregar estadísticas sobre la marcha a través de un Tablero y Pymongo, pero creo que tanto el mapeo/reducción como la agregación con $ group aún pueden ser relativamente lentos. Estoy buscando agregar 60,080 (1 semana valor) de estadísticas de datos de 10 segundos a partir de un script de Python que ya lo recopila en las estadísticas de 10 segundos de los archivos de registro de Apache. – sam0673

+0

¡Feliz de ayudar! He actualizado mi respuesta anterior con algunas notas sobre Map Reduce. – Marc

+0

Una pregunta importante sin embargo ... ¿cómo realizarías db.newStats.aggregate ({$ unwind: "$ hitsPerOneSecond"} ... etc a través de pymongo ya que no puedo encontrar ninguna documentación para esto? – sam0673

13

Cómo $unwind más de una matriz? ¿Has probado $unwinding varias veces? :)

db.newStats.aggregate([ 
    {$unwind: "$hitsPerOneSecond"}, 
    {$unwind: "$pathStats_xxx_api_get_response"}, 

    {$group:{ 
     _id:"$startTimeStr", 
     totalHits: {$sum: "$hitsPerOneSecond"}, 
     totalHitsCount: {$sum: 1}, 
     avgHit: {$avg: "$hitsPerOneSecond"}, 
     minHit: {$min:"$hitsPerOneSecond"}, 
     maxHit:{$max: "$hitsPerOneSecond"}, 

     totalResponses: {$sum: "$pathStats_xxx_api_get_response"}, 
     . . . 
    }} 
]); 

Recuerde que el marco agregada toma una matriz como una entrada (tenga en cuenta que añadí [, ]). En la matriz, puede agregar a la canalización tantas funciones agregadas como desee (citación necesitada) y la salida de cualquier paso será la entrada de la siguiente.

NOTA:

No olvide que si se intenta $unwind en una clave no existente o en una matriz vacía que terminan con ningún documento en absoluto! Es como multiplicar por 0 Supongo ... Así que con varias (posiblemente muchas) $unwind, las posibilidades de lidiar con fantasmas aumentan: Si cualquiera de las matrices involucradas está vacía, todo el documento se pierde y no obtiene nada para ninguno de sus $group agregaciones ...

Cuestiones relacionadas