2010-11-19 9 views
18

Tengo una colección de md5 en mongodb. Me gustaría encontrar todos los duplicados. La columna md5 está indexada. ¿Conoces alguna forma rápida de hacer eso usando map reduce. ¿O debería simplemente iterar sobre todos los registros y buscar duplicados manualmente?Forma rápida de encontrar duplicados en la columna indexada en mongodb

Mi enfoque actual utilizando MapReduce itera sobre la colección casi el doble (suponiendo que no es muy pequeña cantidad de duplicados):

res = db.files.mapReduce(
    function() { 
     emit(this.md5, 1); 
    }, 
    function (key, vals) { 
     return Array.sum(vals); 
    } 
) 

db[res.result].find({value: {$gte:1}}).forEach(
function (obj) { 
    out.duplicates.insert(obj) 
}); 

Respuesta

30

La manera más fácil de hacerlo en una pasada es ordenar por md5 y luego procesarlo adecuadamente.

Algo así como:

var previous_md5; 
db.files.find({"md5" : {$exists:true} }, {"md5" : 1}).sort({ "md5" : 1}).forEach(function(current) { 

    if(current.md5 == previous_md5){ 
    db.duplicates.update({"_id" : current.md5}, { "$inc" : {count:1} }, true); 
    } 

    previous_md5 = current.md5; 

}); 

Ese pequeño script ordena las entradas MD5 y bucles a través de ellos en orden. Si se repite un md5, estarán "back-to-back" después de la clasificación. Así que mantenemos un puntero a previous_md5 y lo comparamos current.md5. Si encontramos un duplicado, lo dejo caer en la colección duplicates (y usando $ inc para contar el número de duplicados).

Esta secuencia de comandos significa que solo tiene que recorrer el conjunto de datos primario una vez. Luego puede recorrer la colección duplicates y realizar la limpieza.

5

Usted puede hacer un grupo por ese campo y luego consulta para obtener el duplicado (teniendo un conteo> 1). http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Group

Aunque, lo más rápido podría ser hacer una consulta que solo devuelva ese campo y luego hacer la agregación en el cliente. Group/Map-Reduce la necesidad de proporcionar acceso a todo el documento, que es mucho más costoso que simplemente proporcionar los datos del índice (que ahora se cubre en 1.7.3+).

Si este es un problema general que necesita ejecutar periódicamente, es posible que desee mantener una colección que sea solo {md5: value, count: value} para que pueda omitir la agregación, y será extremadamente rápido cuando necesidad de eliminar duplicados.

+0

No puedo usar gruping porque está limitado a 10k elementos (tengo 3M). Pero la nota de que MR devolverá solo los datos del índice es interesante. No lo sabía. ¡Gracias! (+1) –

61

Personalmente encontré que en las bases de datos grandes (1TB y más) la respuesta aceptada es terriblemente lenta. La agregación es mucho más rápida. Ejemplo es el siguiente:

db.places.aggregate(
    { $group : {_id : "$extra_info.id", total : { $sum : 1 } } }, 
    { $match : { total : { $gte : 2 } } }, 
    { $sort : {total : -1} }, 
    { $limit : 5 } 
    ); 

Se busca en documentos cuyo extra_info.id se usa dos veces o más veces, ordena los resultados en orden dada campo e imprime primero 5 valores de la misma descendente.

+1

No veo muy bien cómo funciona su solución en los datos de preguntas. ¿Debería la línea de grupo ser '{$ group: {'md5':" $ extra_info.md5 ", total: {$ suma: 1}}},'? – zhon

+2

@zhon No. ¿Has leído la documentación? Dice 'Para este campo _id, puede especificar varias expresiones, incluyendo un solo campo de los documentos en la tubería, un valor calculado de una etapa previa, un documento que consta de múltiples campos y otras expresiones válidas, como constante o campos de subdocumento. Puede usar operadores $ project en expresiones para el campo _id. – expert

+4

Para los datos de pregunta, la línea de grupo debe ser: {$ group: {_id: "$ md5", total: {$ suma: 1}}} , – kdkeck

Cuestiones relacionadas