Подтвердить что ты не робот

Как вернуть большое количество строк из mongodb с помощью node.js http-сервера?

У меня есть пользовательская база данных в mongodb, которую я бы хотел экспортировать через интерфейс REST в JSON. Проблема в том, что в худшем случае количество возвращенных строк составляет более 2 миллионов.

Сначала я попробовал это

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');

который не работает при нехватке памяти. В примере используется node -mongodb-native драйвер и базовый http-пакет.

FATAL ERROR: CALL_AND_RETRY_2 Не удалось выполнить выделение - процесс из памяти

(обратите внимание, что в реальном сценарии я использую параметры, которые ограничивают результаты по мере необходимости, но этот пример запрашивает их все, что является наихудшим сценарием независимо)

Сами данные просты, например

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

Я также пробовал что-то вроде

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

но это тоже закончилось.

Как мне продолжить? Должен быть способ потоковой передачи данных по строкам, но я не смог найти подходящий пример для него. Подкачка данных не может быть и речи из-за требований внешнего приложения. Я думал записать данные в файл, а затем публиковать его, но это приводит к нежелательным io.

4b9b3361

Ответ 1

Метод cursor.streamRecords() встроенного драйвера MongoDB устарел, метод stream() выполняется быстрее.

Я проанализировал 40 000 000 строк документа без проблем с помощью MongoDB + stream() + process.nextTick()

Ответ 2

Я узнал, что объект node -mongodb-native Cursor имеет возможность потоковой передачи (используется с collection.find().streamRecords()) для записей, даже если он не упоминается на странице github драйвера. Смотрите Исходный код курсора и найдите "streamRecords".

В конце код оказался следующим:

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();
  });
});

Ответ 3

Что-то вроде должно работать. Если это не так, вероятно, вы должны открыть проблему в mongodb-native 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: это просто заглушка, я имею в виду, что я не помню точный синтаксис, но это функция each, которую вы ищете.

Ответ 4

Ну, я больше не использую собственный драйвер javascript mongodb, но в mongoose существует довольно хорошая реализация потоков.

Синтаксис двух драйверов довольно похож. Вы можете сделать это с помощью мангуста:

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();
});

Ответ 5

Небольшой модуль для этого, используя Node stream.Transform класс:

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);
};

Вы можете изменить части JSON.Stringify, чтобы сделать любой другой вид "на лету", преобразовывая объекты, идущие от курсора mongodb, и сохраняйте некоторую память.