2012-05-11 9 views
17

Mam bazy danych użytkowników w mongodb, które chciałbym eksportować za pośrednictwem interfejsu REST w JSON. Problem polega na tym, że w najgorszym przypadku ilość zwróconych wierszy wynosi znacznie ponad 2 miliony.Jak zwrócić dużą liczbę wierszy z mongodb przy użyciu serwera http node.js?

Najpierw próbowałem to

var mongo = require('mongodb'), 
    Server = mongo.Server, 
    Db = mongo.Db; 
var server = new Server('localhost', 27017, {auto_reconnect: true}); 
var db = new Db('tracking', server); 
var http = require('http'); 

http.createServer(function (request, response) { 
    db.collection('users', function(err, collection) { 
    collection.find({}, function(err, cursor){ 
     cursor.toArray(function(err, items) { 
     output = '{"users" : ' + JSON.stringify(items) + '}'; 

     response.setHeader("Content-Type", "application/json"); 
     response.end(output); 
     }); 
    }); 
    }); 
}).listen(8008); 
console.log('Server running at localhost:8008'); 

który zawodzi, gdy zabraknie pamięci. W przykładzie użyto sterownika macierzystego node-mongodb i podstawowego pakietu http.

FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory

(zauważ, że w realnym scenariuszu I wykorzystać parametry, które ograniczają wyniki, ile potrzeba, ale ten przykład odpytuje je wszystko co jest najgorszym przypadku niezależnie)

Sam danych jest proste, jak

{ "_id" : ObjectId("4f993d1c5656d3320851aadb"), "userid" : "80ec39f7-37e2-4b13-b442-6bea57472537", "user-agent" : "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322)", "ip" : "127.0.0.1", "lastupdate" : 1335442716 }

próbowałem też coś

while(cursor != null) 
{ 
    cursor.nextObject(function(err, item) { 
    response.write(JSON.stringify(item)); 
    }); 
} 

ale zabrakło także pamięć.

Jak powinienem kontynuować? Powinien istnieć sposób przesyłania danych wiersz po wierszu, ale nie byłem w stanie znaleźć odpowiedniego dla niego przykładu. Przesyłanie stron danych nie wchodzi w grę z powodu zewnętrznych wymagań aplikacji. Pomyślałem o zapisaniu danych do pliku, a następnie opublikowaniu go, ale to prowadzi do niechcianego io.

Odpowiedz

15

cursor.streamRecords() metoda rodzimej MongoDB kierowcy jest przestarzała, metoda stream() jest szybsze.

Mam analizowany dokument 40000000 wiersza acatalog bez problemów z Mongodb + stream() + process.nextTick()

+1

Zauważyłem, że 'cursor.stream()' wykonuje dokładnie to samo, co 'cursor.each()'. – Meekohi

+0

Pamiętaj, aby podać wartość dla 'batchSize' dla tysięcy lub milionów wierszy – alexishacks

+3

Czy możesz wkleić swój pełny kod tutaj – parkerproject

2

Cóż, nie używam już macierzystego sterownika javascript mongodb, ale w mongoose istnieje całkiem dobra implementacja strumieni.

Składnia tych dwóch sterowników jest podobna. Można to zrobić z mangusta:

response.setHeader("Content-Type", "application/json"); 
var stream = collection.find().stream(); 
stream.on('data', function(doc) { 
    response.write(doc); 
}); 
stream.on('close', function() { 
    response.end(); 
}); 
+1

Mongoose byłby lepszym sposobem rozwiązania problemu przechowywania danych. Twoja odpowiedź poprowadziła mnie we właściwym kierunku, kiedy używałem właśnie tego sterownika i odkryłem, że węzeł-mongodb-native ma również opcję strumieniowania w Cursor, zwaną 'streamResults'. Opublikuję pełną odpowiedź na mój problem, używając tylko węzła-mongodb-native później. – Timo

4

coś w tym powinien pracy. Jeśli nie, prawdopodobnie powinieneś otworzyć problem w numerze mongodb-native bug tracker.

http.createServer(function (request, response) { 
    db.collection('users', function(err, collection) { 
    collection.find({}, function(err, cursor){ 
     response.setHeader("Content-Type", "application/json"); 
     cursor.each(function(err, item) { 
     if (item) { 
      response.write(JSON.stringify(item)); 
     } else { 
      response.end(); 
     } 
     }); 
    }); 
    }); 
}).listen(8008); 

PS: to tylko stub, to znaczy ja nie pamiętam dokładnej składni, ale to each funkcja szukasz.

+0

Właściwie to też próbowałem, ale wygląda na to, że funkcja 'toArray' w moim oryginalnym pytaniu faktycznie owija/używa tej funkcji, więc nie powiodło się, gdy skryptowi zabrakło pamięci. – Timo

+0

Tak, toArray musi buforować całą tablicę, więc to nie pomoże, ale kursor będzie działał. Trzeba po prostu otoczyć go nawiasami. – danmactough

+0

Teraz, gdy spróbowałem ponownie, to też działa. Z jakiegoś powodu nie udało się to wcześniej i muszę się cofnąć i sprawdzić, co zrobiłem źle. – Timo

8

Dowiedziałem się, że macierzysty-mongodb-natywny obiekt Cursor ma opcję przesyłania strumieniowego (używaną z collection.find().streamRecords()) również dla rekordów, nawet jeśli nie jest wymieniona w github page of the driver. Zobacz Cursor source code i wyszukaj "streamRecords".

W końcu kod kończący się tak:

db.collection('users', function(err, collection) { 
    var first = true; 

    response.setHeader("Content-Type", "application/json"); 
    response.write('{"users" : ['); 

    var stream = collection.find().streamRecords(); 

    stream.on('data', function(item) { 
    var prefix = first ? '' : ', '; 
    response.write(prefix + JSON.stringify(item)); 
    first = false; 
    }); 
    stream.on('end', function() { 
    response.write(']}'); 
    response.end(); 
    }); 
}); 
+0

Dziękuję Timo za udostępnienie Twojego rozwiązania! – asuciu

1

Trochę modułu to zrobić za pomocą węzła stream.Transform Klasa:

var stream = require('stream'); 

function createCursorStream(){ 

    var cursorStream = new stream.Transform({objectMode:true}); 

    cursorStream._transform = function(chunk,encoding,done){ 
     if(cursorStream.started){ 
      cursorStream.push(', ' + JSON.stringify(chunk)); 
     }else{ 
      cursorStream.push('[' + JSON.stringify(chunk)); 
      cursorStream.started = true; 
     } 
     done(); 
    }; 

    cursorStream._flush = function(done){ 
     cursorStream.push(']'); 
     done(); 
    }; 

    return cursorStream; 
} 

module.exports.streamCursorToResponse = function(cursor,response){ 
    cursor.stream().pipe(createCursorStream()).pipe(response); 
}; 

Można zmieniać JSON.Stringify części do zrobienia każdy inny rodzaj "w locie" przekształca obiekty pochodzące z kursora mongody i pozwala zaoszczędzić trochę pamięci.

Powiązane problemy