2011-03-13 3 views
2

Je ne comprends toujours pas complètement comment fonctionne map/reduce, alors j'ai pensé donner un exemple d'un problème que je dois résoudre, et j'espère que la réponse m'aidera à comprendre concept.MongoDB: Données agrégées en utilisant Map/Reduce

Je suis suivi de pages vues en utilisant une structure de document similaire à ceci:

{ 
    "timestamp" : 1299990045, 
    "visitor" : { 
     "region" : { 
      "country_code" : "US", 
     }, 
     "browser" : { 
      "name" : "IE", 
      "version" : "8.0", 
     } 
    }, 
    "referer" : { 
     "host" : "www.google.com", 
     "path" : "/", 
     "query" : "q=map%2Freduce" 
    } 
} 

je stocke un seul document pour chaque page vue. Étant donné que je reçois environ 15 millions de pages vues par jour, j'aimerais agréger ces résultats chaque nuit, enregistrer les résultats globaux pour ce jour, puis supprimer la collection pour commencer à stocker de nouveau les pages vues. Je veux que la sortie de la map/reduce à ressembler à ceci:

{ 
    "day" : "Sun Mar 13 2011 00:00:00 GMT-0400 (EDT)", 
    "regions" : { 
     "US" : 235, 
     "CA" : 212, 
     "JP" : 121 
    }, 
    "browsers" : { 
     "IE" : 145, 
     "Firefox" : 245, 
     "Chrome" : 95, 
     "Other" : 120 
    }, 
    "referers" : { 
     "www.google.com" : 24, 
     "yahoo.com" 56 
    } 
} 

Je ne sais vraiment pas où commencer à faire ce genre de chose. Toute aide serait appréciée.

Répondre

10

Le processus typique d'écriture d'un travail de réduction de la carte consiste à commencer par le format de données souhaité en sortie de votre réduire, à créer une fonction de mappe qui le génère, puis une fonction de réduction qui les ajoute. Dans votre exemple, vous feriez quelque chose comme ceci:

function map() { 
    var date = new Date(this.timestamp.getFullYear(), 
         this.timestamp.getMonth(), 
         this.timestamp.getDay()); 
    var out = { regions: {}, browsers: {}, referers: {} }; 
    out.regions[ this.visitor.region.country_code ] = 1; 
    out.browsers[ this.visitor.browser.name ] = 1; 
    out.referers[ this.referer.host ] = 1; 
    emit(date, out); 
} 

function reduce(key, values) { 
    var out = { regions: {}, browsers: {}, referers: {} }; 
    values.forEach(function(value) { 
     for(var region in value.regions) { 
     if(out.regions[region]) { 
      out.regions[ region ] += value[region]; 
     } else { 
      out.regions[ region ] = value[region]; 
     } 
     }; 
     for(var browser in value.browsers) { 
     if(out.browsers[browser]) { 
      out.browsers[ browser ] += value[browser]; 
     } else { 
      out.browsers[ browser ] = value[browser]; 
     } 
     }; 
     for(var referer in value.referers) { 
     if(out.referers[ referer]) { 
      out.referers[ referer ] += value[referer]; 
     } else { 
      out.referers[ referer ] = value[referer]; 
     } 
     } 
    }); 
    return out; 
} 

A la fin de cela, vous devriez avoir une collection de sortie qui ressemble à ceci:

{ 
    _id: "Sun Mar 13 2011 12:23:58 GMT-0700 (PDT)", 
    value: { 
    regions: { 
     "US" : 235, 
     "CA" : 212, 
     "JP" : 121 
    }, 
    browsers: { 
     "IE" : 145, 
     "Firefox" : 245, 
     "Chrome" : 95, 
     "Other" : 120 
    }, 
    referers: { 
     "www.google.com" : 24, 
     "yahoo.com" 56 
    } 
    } 
} 

Notez qu'il ya une autre façon, vous pouvez le faire .. Plutôt que de faire un travail de réduction de la carte, vous pouvez également conserver toutes ces données en temps réel en utilisant des incréments atomiques et upsert.

Par exemple, chaque fois que vous générez un des docs votre vue page, vous pouvez également faire une mise à jour comme celui-ci:

db.pageviews.summaries.update({ _id: new Date(this.timestamp.getFullYear(), 
               this.timestamp.getMonth(), 
               this.timestamp.getDay()) }, 
           { $inc : { 
            'visitor.region.US' : 1, 
            'visitor.browser.IE' : 1, 
            'referer.www.google.com' : 1 
            } 
           }, 
           true // upsert 
          ); 

Cela signifie que vous avez toujours votre document de synthèse à jour et n » t besoin de n'importe quelle carte pour réduire les travaux.

Notez, vous pourriez vouloir échapper le '.' Dans vos noms de domaine, Mongo interprétera cela comme une hiérarchie de documents plutôt qu'un nom d'attribut.

+0

Vous avez presque certainement raison à propos de upserts comme je le fais. Faire une carte/réduire l'opération sur 15 millions de documents prendrait FOREVER. Merci pour la carte/réduire l'exemple si. Le concept a plus de sens maintenant. Map = construire une paire valeur/clé pour chaque document, réduire = agréger toutes ces paires valeur/clé. – mellowsoon

+0

Note supplémentaire, je pense que je vais devoir regarder dans une fonction de finalisation. Après tout, je n'aurais pas besoin de tous les pays visités un jour donné. Seulement le top 20. Est-ce là où et comment une fonction de finalisation est utilisée? – mellowsoon

+0

Eh bien, vous pouvez le faire dans un finalize(), mais ce serait un peu inefficace parce que vous seriez en train de faire un tas d'agrégations que vous alliez juste jeter quand c'est fait. S'il y a un moyen de ne pas traiter les pays indésirables en premier lieu, ce serait préférable. Mais en général, oui vous pouvez faire ce genre de filtrage dans une finalisation. – jared

Questions connexes