Когда закрыть соединение базы данных MongoDB в Nodejs

Работа с Nodejs и MongoDB через Node родной драйвер MongoDB. Необходимо получить некоторые документы и внести изменения, а затем сохранить их обратно. Это пример:

db.open(function (err, db) {
  db.collection('foo', function (err, collection) {
    var cursor = collection.find({});
    cursor.each(function (err, doc) {
      if (doc != null) {
        doc.newkey = 'foo'; // Make some changes
        db.save(doc); // Update the document
      } else {
        db.close(); // Closing the connection
      }
    });
  });
});

С асинхронным характером, если процесс обновления документа занимает больше времени, тогда, когда курсор достигает конца документов, соединение с базой данных закрывается. Не все обновления сохраняются в базе данных.

Если параметр db.close() опущен, все документы будут правильно обновлены, но приложение зависает, никогда не выходит.

Я видел сообщение, предлагающее использовать счетчик для отслеживания количества обновлений, когда он возвращается к нулю, а затем закрывает db. Но я делаю что-то не так? Каков наилучший способ справиться с такой ситуацией? Использовать db.close() для освобождения ресурса? Или нужно открыть новое соединение db?

Ответ 1

Здесь потенциальное решение, основанное на подходе подсчета (я его не тестировал и не обнаружил ошибки, но должен передать идею).

Основная стратегия: Получить счетчик количества записей, которые необходимо обновить, сохранить каждую запись асинхронно и обратный вызов успеха, что уменьшит счетчик и закроет БД, если счетчик достигнет 0 (когда последнее обновление закончится). Используя {safe:true}, мы можем гарантировать, что каждое обновление будет успешным.

Сервер mongo будет использовать один поток для каждого соединения, поэтому он хорош для: a) закрытия неиспользуемых подключений или b) пула/повторного использования.

db.open(function (err, db) {
  db.collection('foo', function (err, collection) {
    var cursor = collection.find({});
    cursor.count(function(err,count)){
      var savesPending = count;

      if(count == 0){
        db.close();
        return;
      }

      var saveFinished = function(){
        savesPending--;
        if(savesPending == 0){
          db.close();
        }
      }

      cursor.each(function (err, doc) {
        if (doc != null) {
          doc.newkey = 'foo'; // Make some changes
          db.save(doc, {safe:true}, saveFinished);
        }
      });
    })
  });
});

Ответ 2

Лучше всего использовать объединенное соединение, а затем вызвать функцию db.close() в функции очистки в конце срока службы приложения:

process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);

См. http://mongodb.github.io/node-mongodb-native/driver-articles/mongoclient.html

Немного старый поток, но в любом случае.

Ответ 3

Я обнаружил, что использование счетчика может применяться к простому сценарию, но может быть сложным в сложных ситуациях. Вот решение, которое я поднимаю, закрывая соединение с базой данных, когда соединение с базой данных простаивает:

var dbQueryCounter = 0;
var maxDbIdleTime = 5000; //maximum db idle time

var closeIdleDb = function(connection){
  var previousCounter = 0;
  var checker = setInterval(function(){
    if (previousCounter == dbQueryCounter && dbQueryCounter != 0) {
        connection.close();
        clearInterval(closeIdleDb);
    } else {
        previousCounter = dbQueryCounter;
    }
  }, maxDbIdleTime);
};

MongoClient.connect("mongodb://127.0.0.1:27017/testdb", function(err, connection)(
  if (err) throw err;
  connection.collection("mycollection").find({'a':{'$gt':1}}).toArray(function(err, docs) {
    dbQueryCounter ++;
  });   
  //do any db query, and increase the dbQueryCounter
  closeIdleDb(connection);
));

Это может быть общее решение для любых подключений к базе данных. maxDbIdleTime может быть установлено как то же значение, что и время ожидания запроса db или дольше.

Это не очень элегантно, но я не могу придумать лучшего способа сделать это. Я использую NodeJs для запуска script, который запрашивает MongoDb и Mysql, а script висит там навсегда, если соединения с базой данных не закрыты должным образом.

Ответ 4

Основываясь на предположении от @mpobrien выше, я нашел модуль async, чтобы быть невероятно полезным в этом отношении. Вот пример шаблона, который я придумал:

const assert = require('assert');
const async = require('async');
const MongoClient = require('mongodb').MongoClient;

var mongodb;

async.series(
    [
        // Establish Covalent Analytics MongoDB connection
        (callback) => {
            MongoClient.connect('mongodb://localhost:27017/test', (err, db) => {
                assert.equal(err, null);
                mongodb = db;
                callback(null);
            });
        },
        // Insert some documents
        (callback) => {
            mongodb.collection('sandbox').insertMany(
                [{a : 1}, {a : 2}, {a : 3}],
                (err) => {
                    assert.equal(err, null);
                    callback(null);
                }
            )
        },
        // Find some documents
        (callback) => {
            mongodb.collection('sandbox').find({}).toArray(function(err, docs) {
                assert.equal(err, null);
                console.dir(docs);
                callback(null);
            });
        }
    ],
    () => {
        mongodb.close();
    }
);

Ответ 5

Я придумал решение, в котором используется счетчик. Это не зависит от вызова count() и не ждет тайм-аута. Он закроет db после того, как все документы в каждом() будут исчерпаны.

var mydb = {}; // initialize the helper object.

mydb.cnt = {}; // init counter to permit multiple db objects.

mydb.open = function(db) // call open to inc the counter.
{
  if( !mydb.cnt[db.tag] ) mydb.cnt[db.tag] = 1;
  else mydb.cnt[db.tag]++;
}; 

mydb.close = function(db) // close the db when the cnt reaches 0.
{
  mydb.cnt[db.tag]--;
  if ( mydb.cnt[db.tag] <= 0 ) {
    delete mydb.cnt[db.tag];
    return db.close();
  }
  return null;
};

Чтобы каждый раз, когда вы делаете вызов типа db.each() или db.save(), вы должны использовать эти методы для обеспечения готовности db во время работы и закрытия по завершении.

Пример из OP:

foo = db.collection('foo');

mydb.open(db); // *** Add here to init the counter.**  
foo.find({},function(err,cursor)
{
  if( err ) throw err; 
  cursor.each(function (err, doc)
  {
    if( err ) throw err;
    if (doc != null) {
      doc.newkey = 'foo';
      mydb.open(db); // *** Add here to prevent from closing prematurely **
      foo.save(doc, function(err,count) {
        if( err ) throw err;
        mydb.close(db); // *** Add here to close when done. **
      }); 
    } else {
      mydb.close(db); // *** Close like this instead. **
    }
  });
});

Теперь это предполагает, что от второго до последнего обратного вызова от каждого из них проходит через mydb.open() до того, как последний обратный вызов от каждого переходит к mydb.close().... так что, конечно, дайте мне знать, если это проблема.

Итак: поместите mydb.open(db) перед вызовом db и поместите mydb.close(db) в точку возврата обратного вызова или после вызова db (в зависимости от типа вызова).

Мне кажется, что этот счетчик должен поддерживаться внутри объекта db, но это мое текущее решение. Возможно, мы могли бы создать новый объект, который принимает db в конструкторе и обертывает функции mongodb, чтобы лучше обрабатывать результат.

Ответ 6

Вот решение, которое я придумал. Он избегает использования toArray, и он довольно короткий и сладкий:

var MongoClient = require('mongodb').MongoClient;

MongoClient.connect("mongodb://localhost:27017/mydb", function(err, db) {
  let myCollection = db.collection('myCollection');
  let query = {}; // fill in your query here
  let i = 0;
  myCollection.count(query, (err, count) => { 
    myCollection.find(query).forEach((doc) => {
      // do stuff here
      if (++i == count) db.close();
    });
  });
});