ti-enxame.com

Quando fechar a conexão com o banco de dados MongoDB no Nodejs

Trabalhando com Nodejs e MongoDB por meio do driver nativo Node MongoDB. Precisa recuperar alguns documentos, fazer modificações e salvá-los de volta. Isto é um exemplo:

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

Com natureza assíncrona, se o processo de atualização do documento demorar mais, quando o cursor chegar ao final dos documentos, a conexão com o banco de dados será fechada. Nem todas as atualizações são salvas no banco de dados.

Se o db.close() for omitido, todos os documentos serão atualizados corretamente, mas o aplicativo trava, nunca sai.

Vi uma postagem sugerindo o uso de um contador para rastrear o número de atualizações, quando voltar a zero e fechar o banco de dados. Mas estou fazendo algo errado aqui? Qual é a melhor maneira de lidar com esse tipo de situação? db.close() tem que ser usado para liberar recursos? Ou uma nova conexão db precisa ser aberta?

68
realguess

Aqui está uma solução potencial com base na abordagem de contagem (eu não a testei e não há captura de erro, mas deve transmitir a ideia).

A estratégia básica é: Adquira a contagem de quantos registros precisam ser atualizados, salve cada registro de forma assíncrona e um retorno de chamada com êxito, o que diminuirá a contagem e fechará o banco de dados se a contagem atingir 0 (quando a última atualização for concluída). Usando {safe:true}, podemos garantir que cada atualização seja bem-sucedida.

O servidor mongo usará um encadeamento por conexão; portanto, é bom a) fechar conexões não utilizadas ou b) agrupar/reutilizá-las.

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

É melhor usar uma conexão em pool e chamar db.close () na função de limpeza no final da vida útil do seu aplicativo:

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

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

Um pouco velho, mas de qualquer maneira.

14
pkopac

Eu descobri que o uso do contador pode se aplicar a um cenário simples, mas pode ser difícil em situações complicadas. Aqui está uma solução que eu venho fechando a conexão com o banco de dados quando a conexão com o banco de dados está ociosa:

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

Essa pode ser uma solução geral para qualquer conexão com o banco de dados. maxDbIdleTime pode ser definido como o mesmo valor que o tempo limite da consulta db ou mais.

Isso não é muito elegante, mas não consigo pensar em uma maneira melhor de fazer isso. Eu uso os NodeJs para executar um script que consulta o MongoDb e o Mysql, e o script fica suspenso para sempre se as conexões com o banco de dados não estiverem fechadas corretamente.

5
cl yu

Com base na sugestão do @mpobrien acima, achei o módulo async extremamente útil a esse respeito. Aqui está um exemplo de padrão que eu vim a adotar:

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

Aqui está uma solução que eu criei. Evita usar o toArray e é bem curto e agradável:

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

Eu vim com uma solução que envolve um contador como este. Não depende de uma chamada count () nem espera um tempo limite. Ele fechará o banco de dados depois que todos os documentos em cada () estiverem esgotados.

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

Para que cada vez que você faça uma chamada como db.each () ou db.save (), use esses métodos para garantir que o db esteja pronto enquanto estiver trabalhando e fechado quando terminar.

Exemplo do 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. **
    }
  });
});

Agora, isso pressupõe que o penúltimo retorno de chamada de cada um passe pelo mydb.open () antes que o último retorno de chamada de cada um vá para mydb.close () .... então, é claro, deixe-me saber se esse é um questão.

Portanto: coloque mydb.open (db) antes de uma chamada db e coloque mydb.close (db) no ponto de retorno do retorno de chamada ou após a chamada db (dependendo do tipo de chamada).

Parece-me que esse tipo de contador deve ser mantido dentro do objeto db, mas esta é minha solução atual. Talvez possamos criar um novo objeto que tenha um db no construtor e agrupe as funções do mongodb para lidar melhor com o fechamento.

0
JJ Stiff