2010-08-27 8 views
16

J'ai des documents 25k (4 Go en raw json) de données que je veux effectuer quelques opérations javascript pour le rendre plus accessible à mon consommateur de données fin (R), et je voudrais trier de "contrôle de version" ces changements en ajoutant une nouvelle collection pour chaque changement, mais je ne peux pas comprendre comment map/reduce sans le reduce. Je veux un mappage de document en tête-à-tête - je commence avec 25 356 documents en collection_1, et je veux finir avec 25 356 documents en collection_2.mongoDB carte/réduire moins réduire

Je peux pirater avec ceci:

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

Et puis appelez comme:

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

(Mes Mapper uniquement les appels émettent une fois, avec une chaîne comme premier argument et le document final comme deuxième, c'est une collection de ces deuxièmes arguments que je veux vraiment.)

Mais cela semble bizarre et je ne sais pas pourquoi cela fonctionne même, puisque mon appel emit guments dans mon mapper ne sont pas équivalents à l'argument return de mon reducer. De plus, je me retrouve avec un document comme

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

ce qui semble inutile.

En outre, un curseur qui effectue ses propres insertions n'est même pas un dixième aussi rapide que mapReduce. Je ne connais pas suffisamment MongoDB pour l'évaluer, mais je suppose que c'est à peu près 50x plus lent. Existe-t-il un moyen de parcourir un curseur en parallèle? Je me fous si les documents dans mon collection_2 sont dans un ordre différent de ceux dans collection_1.

+0

La raison pour laquelle cela fonctionne est parce que votre émission et réducteur appel * sont * la même chose. Puisque vous utilisez la valeur [0] comme sortie de votre réducteur, alors il doit être exactement le même parce que vous ne l'avez pas changé (il ne fait que traverser votre réducteur). – null

Répondre

3

Mais cela semble maladroit et je ne sais pas pourquoi cela fonctionne même, puisque mes emit arguments d'appel dans mon mappeur ne sont pas équivalents au rendement argument de mon reducer.

Ils sont équivalents. La fonction de réduction prend un tableau de valeurs T et doit renvoyer une seule valeur dans le même format T. Le format de T est défini par votre fonction de carte. Votre fonction de réduction renvoie simplement le premier élément dans le tableau de valeurs, qui sera toujours de type T. C'est pourquoi ça marche :)

Vous semblez être sur la bonne voie. J'ai fait quelques expériences et il semble que vous ne pouvez pas faire un db.collection.save() à partir de la fonction de la carte, mais pouvez le faire à partir de la fonction de réduction. Votre fonction de carte doit simplement construire le format de document dont vous avez besoin:

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

La fonction de carte réutilise l'ID du document d'origine. Cela devrait empêcher toute nouvelle étape de réduction, car aucune valeur ne partagera la même clé.

La fonction de réduction peut simplement renvoyer null. Mais en plus, vous pouvez écrire la valeur dans une collection séparée.

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

    return null; 
} 

Maintenant db.result doit contenir les documents transformés, sans bruit carte-reduce supplémentaire que vous auriez dans la collection temporaire. Je n'ai pas réellement testé cela sur de grandes quantités de données, mais cette approche devrait tirer parti de l'exécution parallélisée des fonctions de réduction de la carte.

+2

Cette manière a pris 523s et a fini avec une collection exactement comme je le voulais, alors que la manière hackish que j'ai décrite dans la question prend 319s. Il est malheureux que je ne puisse pas simplement appeler 'db.coll.mapReduce (myMapperFunc, null, {'out': 'output'})'. Je pense que réduire est en mesure de sauvegarder en lots/insérer un ensemble d'éléments; Je pense que le goulot d'étranglement ici est le 'save()' appelé dans chaque réduire. – chbrown

+1

@chbrown: Oui, le 'save()' est fait deux fois pour chaque document; le standard réduire-enregistrer à la collection temporaire, et l'enregistrer explicitement dans une collection séparée. Juste curieux, cette solution est-elle réellement plus rapide que d'utiliser un seul curseur? –

+0

Bonjour à tous, nous avons un problème similaire pour gérer de grands ensembles de données et comme la concaténation de tableaux et la numérisation de documents volumineux ne fonctionnent pas, nous avons suivi l'exemple ci-dessus d'enregistrement du document dans la collection séparée. Cela fonctionne bien mais DB est pendu quand nous faisons n'importe quelle autre opération tout en exécutant mapreduce. Y a-t-il une meilleure idée pour la même chose? – MRK

6

Lorsque vous utilisez map/reduce vous finirez toujours avec

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

Pour retirer la clé value vous devrez utiliser une fonction finalize.

Voici la plus simple que vous pouvez faire pour copier des données d'une collection à l'autre:

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

Puis, quand vous fonctionner normalement:

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

"La fonction finalize ne doit pas accéder à la base de données pour quelque raison que ce soit." - [Documents officiels MongoDB] (http://docs.mongodb.org/manual/reference/command/mapReduce/#mapreduce-finalize-cmd) – bloudermilk

+0

Vrai ... mais néanmoins utile de pouvoir le faire. –

+0

Ceci est un goulot d'étranglement de performance complet et va à l'encontre de Map-Reduce !!! S'il vous plaît, ne faites pas ça. –

0

Je face à la même situation. J'ai été en mesure d'accomplir cela via une requête et une projection Mongo. voir Mongo Query

1

Lorsque vous avez accès au shell mongo, il accepte certaines commandes Javascript et il est plus simple:

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

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