web-dev-qa-db-fra.com

Quand fermer la connexion à la base de données MongoDB dans Nodejs

Travailler avec Nodejs et MongoDB via Node Pilote natif MongoDB. Besoin de récupérer certains documents, de faire des modifications, puis de les sauvegarder immédiatement. Voici un exemple:

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

Avec la nature asynchrone, si le processus de mise à jour du document prend plus de temps, lorsque le curseur atteint la fin des documents, la connexion à la base de données est fermée. Toutes les mises à jour ne sont pas enregistrées dans la base de données.

Si la db.close() est omise, tous les documents sont correctement mis à jour, mais l'application se bloque, ne se ferme jamais.

J'ai vu un message suggérant d'utiliser un compteur pour suivre le nombre de mises à jour, quand il revient à zéro, puis fermez la base de données. Mais est-ce que je fais quelque chose de mal ici? Quelle est la meilleure façon de gérer ce genre de situation? db.close() doit-il être utilisé pour libérer des ressources? Ou faut-il ouvrir une nouvelle connexion db?

68
realguess

Voici une solution potentielle basée sur l'approche de comptage (je ne l'ai pas testée et il n'y a pas de capture d'erreur, mais elle devrait transmettre l'idée).

La stratégie de base est la suivante: acquérir le nombre d'enregistrements à mettre à jour, enregistrer chaque enregistrement de manière asynchrone et un rappel en cas de succès, ce qui diminuera le nombre et fermera la base de données si le nombre atteint 0 (lorsque la dernière mise à jour se termine). En utilisant {safe:true} nous pouvons garantir la réussite de chaque mise à jour.

Le serveur mongo utilisera un thread par connexion, il est donc bon soit a) de fermer les connexions inutilisées, soit b) de les regrouper/réutiliser.

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

Il est préférable d'utiliser une connexion groupée, puis d'appeler db.close () dans la fonction de nettoyage à la fin de la vie de votre application:

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

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

Un fil un peu vieux, mais enfin.

14
pkopac

J'ai trouvé que l'utilisation du compteur peut s'appliquer à un scénario simple, mais peut être difficile dans des situations compliquées. Voici une solution que je propose en fermant la connexion à la base de données lorsque la connexion à la base de données est inactive:

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

Cela peut être une solution générale pour toutes les connexions à la base de données. maxDbIdleTime peut être défini sur la même valeur que le délai d'expiration de la requête db ou plus.

Ce n'est pas très élégant, mais je ne peux pas penser à une meilleure façon de le faire. J'utilise NodeJs pour exécuter un script qui interroge MongoDb et Mysql, et le script se bloque pour toujours si les connexions à la base de données ne sont pas fermées correctement.

5
cl yu

Sur la base de la suggestion de @mpobrien ci-dessus, j'ai trouvé le module async incroyablement utile à cet égard. Voici un exemple de modèle que j'en suis venu à adopter:

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

Voici une solution que j'ai trouvée. Cela évite d'utiliser toArray et c'est assez court et doux:

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

J'ai trouvé une solution qui implique un compteur comme celui-ci. Il ne dépend pas d'un appel count () et n'attend pas de temps mort. Il fermera la base de données une fois tous les documents de chaque () épuisés.

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

Pour que chaque fois que vous effectuez un appel comme db.each () ou db.save (), vous utilisiez ces méthodes pour vous assurer que la base de données est prête pendant le travail et fermée une fois terminée.

Exemple d'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. **
    }
  });
});

Maintenant, cela suppose que l'avant-dernier rappel de chacun passe par mydb.open () avant que le dernier rappel de chacun ne passe à mydb.close () .... donc, bien sûr, faites-moi savoir s'il s'agit d'un problème.

Donc: mettez un mydb.open (db) avant un appel db et placez un mydb.close (db) au point de retour du rappel ou après l'appel db (selon le type d'appel).

Il me semble que ce type de compteur doit être conservé dans l'objet db, mais c'est ma solution de contournement actuelle. Peut-être que nous pourrions créer un nouvel objet qui prend un db dans le constructeur et envelopper les fonctions mongodb pour mieux gérer la fermeture.

0
JJ Stiff