2011-08-31 7 views
32

Próbuję użyć MongoDB do analizy plików dziennika Apache. Utworzyłem kolekcję receipts z dzienników dostępu Apache. Oto skrócona podsumowanie tego, co moi modele wyglądać następująco:W MongoDB mapreduce, w jaki sposób mogę spłaszczyć obiekt wartości?

db.receipts.findOne() 
{ 
    "_id" : ObjectId("4e57908c7a044a30dc03a888"), 
    "path" : "/videos/1/show_invisibles.m4v", 
    "issued_at" : ISODate("2011-04-08T00:00:00Z"), 
    "status" : "200" 
} 

Napisałem MapReduce function że grupy wszystkie dane przez pole issued_at dat. Podsumowuje całkowitą liczbę żądań i zawiera zestawienie liczby żądań dla każdej unikalnej ścieżki. Oto przykład tego, co wyjście wygląda następująco:

db.daily_hits_by_path.findOne() 
{ 
    "_id" : ISODate("2011-04-08T00:00:00Z"), 
    "value" : { 
     "count" : 6, 
     "paths" : { 
      "/videos/1/show_invisibles.m4v" : { 
       "count" : 2 
      }, 
      "/videos/1/show_invisibles.ogv" : { 
       "count" : 3 
      }, 
      "/videos/6/buffers_listed_and_hidden.ogv" : { 
       "count" : 1 
      } 
     } 
    } 
} 

Jak mogę dokonać wygląd wyjściowy takiego Zamiast:

{ 
    "_id" : ISODate("2011-04-08T00:00:00Z"), 
    "count" : 6, 
    "paths" : { 
     "/videos/1/show_invisibles.m4v" : { 
      "count" : 2 
     }, 
     "/videos/1/show_invisibles.ogv" : { 
      "count" : 3 
     }, 
     "/videos/6/buffers_listed_and_hidden.ogv" : { 
      "count" : 1 
     } 
    } 
} 

Odpowiedz

12

Obecnie nie jest to możliwe, ale sugerowałbym głosowanie na tę sprawę: https://jira.mongodb.org/browse/SERVER-2517.

+0

To jest właściwa odpowiedź, więc zagłosuj na tę sprawę, a następnie użyj http://stackoverflow.com/a/18124090/1402121 jako swojego rozwiązania – dmo

4

AFAIK, przez projektowanie Mongo Mapa zmniejszyć będą pluć wyniki w „krotki wartości "i nie widziałem niczego, co skonfiguruje ten" format wyjściowy ". Może być użyta metoda finalize().

Można spróbować uruchomić post-proces, który będzie przekształcenia danych za pomocą

results.find({}).forEach(function(result) { 
    results.update({_id: result._id}, {count: result.value.count, paths: result.value.paths}) 
}); 

Tak, że wygląda brzydko. Wiem.

+0

czy nie można bezpośrednio zmodyfikować obiektu/dokumentu wyniku? –

3

Podobne podejście do tej @ljonas ale nie trzeba hardcode pól dokumentu:

db.results.find().forEach(function(result) { 
    var value = result.value; 
    delete value._id; 
    db.results.update({_id: result._id}, value); 
    db.results.update({_id: result.id}, {$unset: {value: 1}}) 
}); 
4

Można zrobić kodu Dana z odniesieniem kolekcji:

function clean(collection) { 
     collection.find().forEach(function(result) { 
     var value = result.value; 
     delete value._id;  
     collection.update({_id: result._id}, value);  
     collection.update({_id: result.id}, {$unset: {value: 1}}) })}; 
7

Biorąc najlepsze z poprzednich odpowiedzi i komentarze:

db.items.find().hint({_id: 1}).forEach(function(item) { 
    db.items.update({_id: item._id}, item.value); 
}); 

Od http://docs.mongodb.org/manual/core/update/#replace-existing-document-with-new-document
"Jeśli argument update zawiera tylko pary pól i wartości, metoda update() zastępuje istniejący dokument dokumentem w argumencie update, z wyjątkiem pola _id."

Nie musisz więc ani $unset value, ani listy poszczególnych pól.

Od https://docs.mongodb.com/manual/core/read-isolation-consistency-recency/#cursor-snapshot „kursory MongoDB może wrócić tego samego dokumentu więcej niż raz w niektórych sytuacjach. ... wykorzystać unikalny indeks na tym polu czy te pola tak, że zapytanie zwróci każdy dokument nie więcej niż jeden raz. Frazę podpowiedź(), aby wyraźnie wymusić zapytanie o użycie tego indeksu. "

+0

Prowadzi to do poważnych warunków wyścigowych. –

+0

@DerekBrown, dzięki, naprawiłem to teraz –

+0

nie, ty nie ... –

3

Wszystkie proponowane rozwiązania są dalekie od optymalnych. Najszybciej można to zrobić tak daleko jest coś takiego:

var flattenMRCollection=function(dbName,collectionName) { 
    var collection=db.getSiblingDB(dbName)[collectionName]; 

    var i=0; 
    var bulk=collection.initializeUnorderedBulkOp(); 
    collection.find({ value: { $exists: true } }).addOption(16).forEach(function(result) { 
     print((++i)); 
     //collection.update({_id: result._id},result.value); 

     bulk.find({_id: result._id}).replaceOne(result.value); 

     if(i%1000==0) 
     { 
      print("Executing bulk..."); 
      bulk.execute(); 
      bulk=collection.initializeUnorderedBulkOp(); 
     } 
    }); 
    bulk.execute(); 
}; 

Następnie nazwać: flattenMRCollection("MyDB","MyMRCollection")

Jest to znacznie szybciej niż robi kolejne aktualizacje.

+3

Uwaga: jest to nowość w MongoDB 2.6 – Vincent

0

Podczas eksperymentów z odpowiedzią Vincenta, znalazłem kilka problemów.Zasadniczo, jeśli wykonasz aktualizacje w pętli foreach, spowoduje to przeniesienie dokumentu na koniec kolekcji, a kursor ponownie dotrze do tego dokumentu (example). Można to obejść, jeśli zostanie użyte $snapshot. Dlatego poniżej zamieszczam przykład Java.

final List<WriteModel<Document>> bulkUpdate = new ArrayList<>(); 

// You should enable $snapshot if performing updates within foreach 
collection.find(new Document().append("$query", new Document()).append("$snapshot", true)).forEach(new Block<Document>() { 
    @Override 
    public void apply(final Document document) { 
     // Note that I used incrementing long values for '_id'. Change to String if 
     // you used string '_id's 
     long docId = document.getLong("_id"); 
     Document subDoc = (Document)document.get("value"); 
     WriteModel<Document> m = new ReplaceOneModel<>(new Document().append("_id", docId), subDoc); 
     bulkUpdate.add(m); 

     // If you used non-incrementing '_id's, then you need to use a final object with a counter. 
     if(docId % 1000 == 0 && !bulkUpdate.isEmpty()) { 
      collection.bulkWrite(bulkUpdate); 
      bulkUpdate.removeAll(bulkUpdate); 
     } 
    } 
}); 
// Fixing bug related to Vincent's answer. 
if(!bulkUpdate.isEmpty()) { 
    collection.bulkWrite(bulkUpdate); 
    bulkUpdate.removeAll(bulkUpdate); 
} 

Uwaga: Ten fragment trwa średnio 7,4 sekundy, aby wykonać na moim komputerze z 100k rekordów i 14 atrybutów (IMDB zestawu danych). Bez grupowania zajmuje to średnio 25,2 sekundy.

Powiązane problemy