MongoDB eindeutigen Wert aggregation über Karte reduzieren
Sehe ich jede Menge Fragen auf, SO dass über aggregation in MongoDB, aber ich habe nicht gefunden, eine komplette Lösung, um mir noch.
Hier ist ein Beispiel meiner Daten:
{
"fruits" : {
"apple" : "red",
"orange" : "orange",
"plum" : "purple"
}
}
{
"fruits" : {
"apple" : "green",
"plum" : "purple"
}
}
{
"fruits" : {
"apple" : "red",
"orange" : "yellow",
"plum" : "purple"
}
}
Nun, mein Ziel ist die Bestimmung der Popularität von jeder Farbe, für jede Frucht, so etwas wie diese wäre die Ausgabe Sammlung:
{
"_id" : "apple"
"values" : {
"red" : 2,
"green" : 1
}
}
{
"_id" : "orange"
"values" : {
"orange" : 1,
"yellow" : 1
}
}
{
"_id" : "plum"
"values" : {
"purple" : 3
}
}
Habe ich versucht verschiedene M/R Funktionen, und am Ende werden Sie entweder nicht funktionieren, oder Sie nehmen exponentiell lang. Im Kontext von Beispiel (Frucht), ich habe über 1000 verschiedene Früchte und 100.000 Farben über ungefähr 10.000.000 insgesamt Dokumente. Meine aktuellen arbeiten M/R ist diese:
map = function() {
if (!this.fruits) return;
for (var fruit in this.fruits) {
emit(fruit, {
val_array: [
{value: this.fruits[fruit], count: 1}
]
});
}
};
reduce = function(key, values) {
var collection = {
val_array: []
};
var found = false;
values.forEach(function(map_obj) {
map_obj.val_array.forEach(function(value_obj) {
found = false;
//if exists in collection, inc, else add
collection.val_array.forEach(function(coll_obj) {
if (coll_obj.value == value_obj.value) {
//the collection already has this object, increment it
coll_obj.count += value_obj.count;
found = true;
return;
}
});
if (!found) {
//the collection doesn't have this obj yet, push it
collection.val_array.push(value_obj);
}
});
});
return collection;
};
Nun, das funktioniert, und für 100 Datensätze, es dauert nur eine Sekunde oder so, aber die Zeit steigt nicht Linear, so 100M Datensätze würde eine sehr lange Zeit. Das problem ist, dass ich einen poor-mans-sub-aggregation in der senken-Funktion mit der collection
array, sodass mir der Iteration über beide collection
und die Werte von meinem map-Funktion. Jetzt brauche ich nur, um herauszufinden, wie dies zu tun, effizient (auch wenn es erfordert, mehrere Ermäßigungen). Alle Vorschläge sind willkommen!
BEARBEITEN aus Mangel an einen besseren Ort, um es zu veröffentlichen, hier meine Lösung.
Zuerst habe ich eine Datei namens mr.js
:
map = function() {
if (!this.fruits) return;
var skip_fruits = {
'Watermelon':1,
'Grapefruit':1,
'Tomato':1 //yes, a tomato is a fruit
}
for (var fruit in this.fruits) {
if (skip_fruits[fruit]) continue;
var obj = {};
obj[this.fruits[fruit]] = 1;
emit(fruit, obj);
}
};
reduce = function(key, values) {
var out_values = {};
values.forEach(function(v) {
for(var k in v) { //iterate values
if (!out_values[k]) {
out_values[k] = v[k]; //init missing counter
} else {
out_values[k] += v[k];
}
}
});
return out_values;
};
var in_coll = "fruit_repo";
var out_coll = "fruit_agg_so";
var total_docs = db[in_coll].count();
var page_size = 100000;
var pages = Math.floor(total_docs / page_size);
print('Starting incremental MR job with '+pages+' pages');
db[out_coll].drop();
for (var i=0; i<pages; i++) {
var skip = page_size * i;
print("Calculating page limits for "+skip+" - "+(skip+page_size-1)+"...");
var start_date = db[in_coll].find({},{date:1}).sort({date:1}).skip(skip).limit(1)[0].date;
var end_date = db[in_coll].find({},{date:1}).sort({date:1}).skip(skip+page_size-1).limit(1)[0].date;
var mr_command = {
mapreduce: in_coll,
map: map,
reduce: reduce,
out: {reduce: out_coll},
sort: {date: 1},
query: {
date: {
$gte: start_date,
$lt: end_date
}
},
limit: (page_size - 1)
};
print("Running mapreduce for "+skip+" - "+(skip+page_size-1));
db[in_coll].runCommand(mr_command);
}
Datei iteriert über meine gesamte Sammlung, schrittweise Karte/reduzieren 100k docs (sortiert nach date
die einen index aufweisen MÜSSEN!) in einer Zeit, und verringern Sie Sie in einer einzigen Ausgabe Sammlung. Man benutzt es wie folgt: mongo db_name mr.js
.
Dann, nach ein paar Stunden, ich habe eine Sammlung mit allen Informationen. Um herauszufinden, welche Früchte haben die meisten Farben, ich benutze diese von der mongo shell drucken Sie die top-25:
//Show number of number of possible values per key
var keys = [];
for (var c = db.fruit_agg_so.find(); c.hasNext();) {
var obj = c.next();
if (!obj.value) break;
var len=0;for(var l in obj.value){len++;}
keys.push({key: obj['_id'], value: len});
}
keys.sort(function(a, b){
if (a.value == b.value) return 0;
return (a.value > b.value)? -1: 1;
});
for (var i=0; i<20; i++) {
print(keys[i].key+':'+keys[i].value);
}
Das wirklich Coole an diesem Ansatz ist, dass, da es inkrementell, an dem ich arbeiten kann mit dem output-Daten während der mapreduce ausgeführt wird.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Es scheint, dass Sie nicht wirklich brauchen
val_array
. Warum nicht eine einfache hash? Versuchen Sie dies:colors
Objekt muss größer werden):"secs_running" : 488, "msg": "m/r: (1/3) emit phase 383999/10752083 3%"
.emit(fruit, {this.fruits[fruit]: 1});
da war der Schlüssel dynamisch generiert, so habe ich diese JS-hack statt:var obj = {}; obj[this.fruits[fruit]] = 1; emit(fruit, obj);
.Tut mir Leid dir das zu sagen, aber die MongoDB MapReduce-framework ist unglaublich langsam und wird wohl auch weiterhin so sein, für "eine Weile" (ich würde nicht erwarten, dass eine Verbesserung auf Ihrer roadmap).
Einfach, meine Antwort wäre, dass würde ich nicht tun, mit Mongo-MapReduce, sondern der Fokus auf der Umsetzung mit Hilfe Der Neuen Aggregation Framework:
http://docs.mongodb.org/manual/reference/aggregation/
oder running Hadoop on top:
http://www.slideshare.net/spf13/mongodb-and-hadoop (einfaches und nettes intro)
Habe ich auch schon Probleme mit MongoDB zu langsam, wenn mit der Implementierung der MapReduce-Funktionalität, und mein Fazit ist, dass, selbst wenn dabei die einfachen Aufgaben, die es nicht einmal in die Nähe der zwei oben genannten Lösungen, wenn es um Leistungen geht. Sie könnte leicht Prozess >1M docs/Sek (oder sogar mehr) auf commodity-hardware mit der neuen aggregation framework.