Wann wird die MongoDB-Datenbankverbindung in Nodejs geschlossen?

76

Arbeiten mit Nodejs und MongoDB über den nativen Node MongoDB-Treiber. Sie müssen einige Dokumente abrufen, Änderungen vornehmen und sie dann sofort wieder speichern. Dies ist ein Beispiel:

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

Bei asynchroner Natur wird die Datenbankverbindung geschlossen, wenn der Aktualisierungsvorgang des Dokuments länger dauert. Wenn der Cursor das Ende der Dokumente erreicht. Nicht alle Updates werden in der Datenbank gespeichert.

Wenn das db.close()weggelassen wird, werden alle Dokumente korrekt aktualisiert, aber die Anwendung hängt, wird nie beendet.

Ich habe einen Beitrag gesehen, in dem vorgeschlagen wurde, einen Zähler zu verwenden, um die Anzahl der Aktualisierungen zu verfolgen. Wenn Sie auf Null zurückfallen, schließen Sie die Datenbank. Aber mache ich hier etwas falsch? Was ist der beste Weg, um mit dieser Art von Situation umzugehen? Muss db.close()verwendet werden, um Ressourcen freizusetzen? Oder muss eine neue Datenbankverbindung geöffnet werden?

Realguess
quelle

Antworten:

25

Hier ist eine mögliche Lösung, die auf dem Zählansatz basiert (ich habe ihn nicht getestet und es gibt keine Fehlerbehebung, aber er sollte die Idee vermitteln).

Die grundlegende Strategie lautet: Ermitteln Sie die Anzahl der zu aktualisierenden Datensätze, speichern Sie jeden Datensatz asynchron und rufen Sie bei Erfolg zurück, wodurch die Anzahl verringert und die Datenbank geschlossen wird, wenn die Anzahl 0 erreicht (wenn die letzte Aktualisierung abgeschlossen ist). Durch die Verwendung können {safe:true}wir sicherstellen, dass jedes Update erfolgreich ist.

Der Mongo-Server verwendet einen Thread pro Verbindung, daher ist es gut, entweder a) nicht verwendete Verbindungen zu schließen oder b) sie zu bündeln / wiederzuverwenden.

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);
        }
      });
    })
  });
});
mpobrien
quelle
5
@realguess, es gibt auch Bibliotheken für Parallelitäts-Utils, die Ihnen dabei helfen können, damit Sie die Details nicht verwalten müssen. Schauen Sie sich async.js an, zum Beispiel github.com/caolan/async
mpobrien
@mpobrien, könnten Sie näher erläutern, wie Sie Async verwenden können, um dieses Problem zu lösen?
Márcio Paiva
Denken Sie, dass diese Lösung auch 2017 noch gültig ist, oder wissen Sie etwas Besseres? Ich habe über so etwas nachgedacht, aber was ist, wenn die Funktion in cursor.each(function (err, doc) {eine asynchrone Funktion aufruft, die somit die Logik in einem Rückruf ausführt und möglicherweise die Datenbank nach each()Abschluss benötigt? Und was ist, wenn dieser Rückruf nach nachfolgenden Änderungen in der Software eine andere asynchrone Funktion aufruft (ich hoffe, Sie haben die Idee)?
wässrig
19

Verwenden Sie am besten eine Poolverbindung und rufen Sie am Ende der Lebensdauer Ihrer Anwendung in der Bereinigungsfunktion db.close () auf:

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

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

Ein bisschen alter Thread, aber trotzdem.

pkopac
quelle
Das bereitet mir tatsächlich Probleme. Wenn ich meinen Dienst neu starte, erhalte ich gelegentlich Mongo-Fehler "Topologie wurde zerstört", da die Verbindungen unterbrochen zu werden scheinen. Mache ich etwas falsch?
ifightcrime
1
@ifightcrime: Klingt nach einer laufenden Abfrage, während Sie die Verbindung geschlossen haben. Hängt davon ab, ob Sie die Abfragen zum Abschluss benötigen. Wenn Sie Schreibvorgänge haben, auf die Sie warten müssen, müssen Sie wahrscheinlich nachverfolgen, dass sie manuell erstellt wurden. Sie können hier herausfinden, wie es genau funktioniert: github.com/mongodb/node-mongodb-native/blob/2.1/lib/db.js#L366
pkopac
@ pkopacs Link in der Antwort - ist nicht so hilfreich zu verstehen, wann die MongoDB-Datenbankverbindung in Nodejs geschlossen werden muss (was die gestellte Frage ist). Der Link von pkopac in den Kommentaren ist defekt. Trotzdem denke ich, dass dies die beste Antwort hier ist und als akzeptierte Antwort markiert werden sollte ...
AS
6

Ich fand heraus, dass die Verwendung von Zählern für einfache Szenarien gelten kann, in komplizierten Situationen jedoch schwierig sein kann. Hier ist eine Lösung, die ich finde, indem ich die Datenbankverbindung schließe, wenn die Datenbankverbindung inaktiv ist:

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

Dies kann eine allgemeine Lösung für alle Datenbankverbindungen sein. maxDbIdleTime kann auf den gleichen Wert wie das Zeitlimit für Datenbankabfragen oder länger festgelegt werden.

Das ist nicht sehr elegant, aber ich kann mir keinen besseren Weg vorstellen, dies zu tun. Ich verwende NodeJs, um ein Skript auszuführen, das MongoDb und MySQL abfragt, und das Skript bleibt dort für immer hängen, wenn die Datenbankverbindungen nicht ordnungsgemäß geschlossen werden.

cl yu
quelle
1
Hey, ich freue mich über die Antwort, aber Sie müssen das clearInterval von closeIdleDb in checker ändern :). Das hat mir wirklich geholfen
RNikoopour
Ziemlich interessant!
wässrig
Einfache und schnelle Lösung. Vielen Dank
Pedram Marandi
2

Hier ist eine Lösung, die ich gefunden habe. Es vermeidet die Verwendung von toArray und ist ziemlich kurz und bündig:

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();
    });
  });
});
Glenn
quelle
Was ist, wenn andere asynchrone Aufrufe, die schließlich in die Datenbank schreiben, in dem // do stuff hereTeil enthalten sind? Finden sie es nicht geschlossen?
wässrig
1

Aufgrund des obigen Vorschlags von @mpobrien habe ich festgestellt, dass das Async- Modul in dieser Hinsicht unglaublich hilfreich ist. Hier ist ein Beispielmuster, das ich übernommen habe:

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();
    }
);
Andrew Kirk
quelle
Können Sie eine Erklärung hinzufügen, wie dies funktioniert und das Problem löst? Für diejenigen wie mich, die Async nicht kennen.
wässrig
@watery bietet die async.series eine Möglichkeit, asynchrone Funktionen in einer Reihe aufzurufen, wobei eine nachfolgende Funktion erst aufgerufen wird, wenn die vorherige erfolgreich abgeschlossen wurde. Es bietet einen optionalen Rückruf am Ende, nachdem alle Funktionen im Array / Objekt erfolgreich abgeschlossen wurden, den ich in diesem Fall verwende, um die Datenbankverbindung endgültig zu schließen.
Andrew Kirk
0

Ich habe eine Lösung gefunden, die einen solchen Zähler beinhaltet. Es hängt weder von einem Aufruf von count () ab, noch wartet es auf eine Auszeit. Die Datenbank wird geschlossen, nachdem alle Dokumente in jedem () erschöpft sind.

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

Damit Sie jedes Mal, wenn Sie einen Aufruf wie db.each () oder db.save () tätigen, diese Methoden verwenden, um sicherzustellen, dass die Datenbank während der Arbeit bereit ist und nach Abschluss geschlossen wird.

Beispiel aus 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. **
    }
  });
});

Dies setzt voraus, dass der vorletzte Rückruf von jedem die mydb.open () durchläuft, bevor der letzte Rückruf von jedem an mydb.close () geht. Lassen Sie mich also natürlich wissen, ob dies ein ist Problem.

Also: Setzen Sie eine mydb.open (db) vor einen db-Aufruf und eine mydb.close (db) am Rückgabepunkt des Rückrufs oder nach dem db-Aufruf (abhängig vom Anruftyp).

Mir scheint, dass diese Art von Zähler innerhalb des Datenbankobjekts beibehalten werden sollte, aber dies ist meine aktuelle Problemumgehung. Vielleicht könnten wir ein neues Objekt erstellen, das eine Datenbank im Konstruktor benötigt und die Mongodb-Funktionen umschließt, um das Schließen besser zu handhaben.

JJ Stiff
quelle
0

Moderne Methode ohne Zähler, Bibliotheken oder benutzerdefinierten Code:

let MongoClient = require('mongodb').MongoClient;
let url = 'mongodb://yourMongoDBUrl';
let database = 'dbName';
let collection = 'collectionName';

MongoClient.connect(url, { useNewUrlParser: true }, (mongoError, mongoClient) => {
   if (mongoError) throw mongoError;

   // query as an async stream
   let stream = mongoClient.db(database).collection(collection)
        .find({}) // your query goes here
        .stream({
          transform: (readElement) => {
            // here you can transform each element before processing it
            return readElement;
          }
        });

   // process each element of stream (async)
   stream.on('data', (streamElement) => {
        // here you process the data
        console.log('single element processed', streamElement);
   });

   // called only when stream has no pending elements to process
   stream.once('end', () => {
     mongoClient.close().then(r => console.log('db successfully closed'));
   });
});

Getestet auf Version 3.2.7 des Mongodb-Treibers, aber laut Link möglicherweise seit Version 2.0 gültig

Sebastián Ezquerro
quelle
0

Hier ein erweitertes Beispiel für die Antwort von pkopac , da ich den Rest der Details herausfinden musste:

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

Hier ist ein Link zum Unterschied zwischen SIGINT und SIGTERM. Ich musste das hinzufügen process.exit(), sonst wurde mein Knoten-Webserver beim Ausführen Ctrl + Cdes laufenden Prozesses in der Befehlszeile nicht sauber beendet .

Rafael Emshoff
quelle