88 votes

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

Travailler avec Nodejs et MongoDB grâce au pilote natif de Node MongoDB. J'ai besoin de récupérer des documents, de les modifier, puis de les sauvegarder. 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, alors 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 le db.close() est omis, tous les documents sont correctement mis à jour, mais l'application se bloque, sans jamais sortir.

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

29voto

mpobrien Points 3305

Voici une solution potentielle basée sur l'approche du comptage (je ne l'ai pas testée et il n'y a pas de piège à erreurs, mais cela devrait donner une idée).

La stratégie de base est la suivante : Acquérir le compte du nombre d'enregistrements à mettre à jour, sauvegarder chaque enregistrement de manière asynchrone et un callback sur le succès, qui décrémentera le compte et fermera la BD si le compte atteint 0 (lorsque la dernière mise à jour se termine). En utilisant {safe:true} nous pouvons nous assurer que chaque mise à jour est réussie.

Le serveur mongo utilisera un thread par connexion, il est donc bon de a) fermer les connexions inutilisées, ou b) les mettre en commun/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);
        }
      });
    })
  });
});

6 votes

@realguess, il existe également des librairies pour les outils de concurrence qui peuvent vous aider à faire ce genre de choses sans que vous ayez à gérer les détails. consultez async.js, par exemple. github.com/caolan/async

0 votes

@mpobrien, pourriez-vous nous expliquer comment utiliser l'asynchronisme pour résoudre ce problème ?

0 votes

Pensez-vous que ces solutions tiennent toujours en 2017 ou connaissez-vous quelque chose de mieux ? Je pensais à quelque chose comme ça, mais que se passerait-il si la fonction en cursor.each(function (err, doc) { appelle une fonction asynchrone, qui exécuterait donc la logique dans un callback et aurait potentiellement besoin de la base de données après l'appel. each() finitions ? Et si, après des modifications ultérieures du logiciel, ce callback appelle une autre fonction asynchrone (j'espère que vous avez compris l'idée) ?

23voto

pkopac Points 41

Il est préférable d'utiliser une connexion mutualisée et 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 peu vieux fil, mais quand même.

7voto

cl yu Points 11

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 j'ai trouvée en fermant la connexion à la base de données lorsque celle-ci 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 de base de données. maxDbIdleTime peut être défini comme la même valeur que db query timeout ou plus.

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

1 votes

Hé, j'apprécie la réponse mais vous devez changer le clearInterval de closeIdleDb à checker :). Cela m'a vraiment aidé

0 votes

Très intéressant !

0 votes

Une solution simple et rapide. Merci à vous

6voto

Rafael Cichocki Points 601

Voici un exemple étendu à la réponse donnée par pkopac puisque je devais m'occuper du reste des détails :

const client = new MongoClient(uri);
(async () => await client.connect())();

// use client to work with db
const find = async (dbName, collectionName) => {
  try {
    const collection = client.db(dbName).collection(collectionName);
    const result = await collection.find().toArray()
    return result;
  } catch (err) {
    console.error(err);
  }
}

const cleanup = (event) => { // SIGINT is sent for example when you Ctrl+C a running process from the command line.
  client.close(); // Close MongodDB Connection when Process ends
  process.exit(); // Exit with default success-code '0'.
}

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

Voici un lien vers la différence entre SIGINT y SIGTERM . J'ai dû ajouter le process.exit() sinon mon serveur web node ne se terminait pas proprement quand on faisait Ctrl + C sur le processus en cours dans la ligne de commande.

2voto

Alan Points 360

Voici une solution que j'ai trouvée. Elle évite d'utiliser toArray et elle est assez courte et douce :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X