2011-12-04 33 views
62

Trabajando con Nodejs y MongoDB mediante el controlador nativo MongoDB de Nodo. Necesita recuperar algunos documentos y hacer modificaciones, luego guárdelos nuevamente. Este es un ejemplo:Cuándo cerrar la conexión de la base de datos MongoDB en Nodejs

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

Con carácter asíncrono, si el proceso de actualización del documento lleva más tiempo, a continuación, cuando el cursor llega al final de los documentos, con base de datos se cierra. No todas las actualizaciones se guardan en la base de datos.

Si se omite el db.close(), todos los documentos se actualizan correctamente, pero la aplicación se bloquea, nunca se cierra.

Vi una publicación sugiriendo usar un contador para rastrear el número de actualizaciones, cuando vuelva a cero, luego cierre el db. ¿Pero estoy haciendo algo mal aquí? ¿Cuál es la mejor manera de manejar este tipo de situación? ¿Se debe usar db.close() para liberar recursos? ¿O debe abrirse una nueva conexión db?

Respuesta

23

Aquí hay una solución potencial basada en el enfoque de conteo (no lo he probado y no hay trampas de error, pero debería transmitir la idea).

La estrategia básica es: Adquirir el recuento de cuántos registros deben actualizarse, guardar cada registro de forma asincrónica y una devolución de llamada en caso de éxito, lo que disminuirá el recuento y cerrará el DB si el recuento llega a 0 (cuando la última actualización acabados). Al usar {safe:true} podemos asegurarnos de que cada actualización sea exitosa.

El servidor mongo usará un hilo por conexión, por lo que es bueno a) cerrar las conexiones no utilizadas, o b) agruparlas/reutilizarlas.

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

@realguess, también existen librerías para utilidades de concurrencia que pueden ayudarle a hacer esto por lo que no tiene que manejar los detalles. echa un vistazo a async.js, por ejemplo, https://github.com/caolan/async – mpobrien

+0

@mpobrien, ¿podrías dar más detalles sobre cómo usar la función asincrónica para resolver este problema? –

+0

¿Crees que estas soluciones aún se mantienen en 2017 o conoces algo mejor? Estaba pensando en algo como esto, pero ¿y si la función en 'cursor.each (función (err, doc) {' llama a una función asíncrona, que ejecutaría lógica en una devolución de llamada y posiblemente necesitaría la base de datos después de 'cada() '¿termina? ¿Y qué pasa si después de los cambios posteriores en el software, esa devolución de llamada llama a otra función asíncrona (espero que entiendas la idea)? – watery

4

Encontré que usar el contador puede aplicarse al escenario simple, pero puede ser difícil en situaciones complicadas. Aquí es una solución que se me ocurre mediante el cierre de la conexión de base de datos cuando la conexión de base de datos está inactivo:

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

Esto puede ser una solución general para las conexiones de bases de datos. maxDbIdleTime se puede establecer como el mismo valor que db query timeout o más.

Esto no es muy elegante, pero no puedo pensar en una mejor manera de hacerlo. Yo uso NodeJs para ejecutar un script que consulta MongoDb y Mysql, y el script se cuelga allí para siempre si las conexiones de la base de datos no se cierran correctamente.

+1

Oye, agradezco la respuesta; sin embargo, necesitas cambiar clearInterval de closeIdleDb a checker :). Esto realmente me ayudó – RNikoopour

+0

¡Muy interesante! – watery

0

Se me ocurrió una solución que implica un contador como este. No depende de una llamada a count() ni espera un tiempo de espera. Cerrará la base de datos después de que se hayan agotado todos los documentos en cada().

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

Así que cada vez que se va a hacer una llamada como db.each() o db.save() debe utilizar estos métodos para asegurar el PP está preparado mientras se trabaja y se cierra cuando haya terminado.

Ejemplo de 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. ** 
    } 
    }); 
}); 

Ahora, esto supone que el segundo al último de devolución de llamada de cada una lo hace a través de la mydb.open() antes de la última devolución de llamada desde cada uno va a mydb.close(). ... así que, por supuesto, avíseme si esto es un problema.

Por lo tanto: poner un mydb.open (db) antes de una llamada a base de datos y poner un mydb.close (db) en el punto de retorno de la devolución de llamada o después de la llamada db (dependiendo del tipo de llamada).

Me parece que este tipo de contador debe mantenerse dentro del objeto db, pero esta es mi solución actual. Tal vez podríamos crear un nuevo objeto que tome un DB en el constructor y ajustar las funciones de mongodb para manejarlo mejor.

13

Lo mejor es utilizar una conexión agrupada y luego llamar db.close() en función de limpieza al final de la vida de su aplicación:

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

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

Un hilo poco viejo, pero de todos modos .

+0

Esto realmente me causa problemas. Ocasionalmente, cuando reinicio mi servicio, recibo errores de "Topología destruida" de Mongo porque parece que las conexiones se cortan. ¿Estoy haciendo algo mal? – ifightcrime

+1

@ifightcrime: suena como una consulta en ejecución, mientras que usted ha cerrado la conexión. Depende si necesita que las consultas terminen. Si tiene que escribir, necesita esperar, creo que debe hacer un seguimiento de que están hechos manualmente. Puede intentar encontrar cómo funciona exactamente aquí: https://github.com/mongodb/node-mongodb-native/blob/2.1/lib/db.js#L366 – pkopac

1

Basado en la sugerencia de @mpobrien anterior, he encontrado que el módulo async es increíblemente útil a este respecto. Aquí hay un patrón de ejemplo que he adoptado:

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

¿Puede agregar alguna explicación sobre cómo funciona esto y lo resuelve? ¿el problema? Para aquellos como yo que no saben asincronizar. – watery

+0

@watery, async.series proporciona una forma de invocar funciones asincrónicas en una serie, donde una función subsiguiente no se invoca hasta que la anterior se haya completado correctamente. Proporciona una devolución de llamada opcional al final, después de que todas las funciones en la matriz/objeto se hayan completado con éxito, lo que estoy usando en este caso para cerrar finalmente la conexión de la base de datos. –

0

Aquí hay una solución que se me ocurrió. Evita el uso de toArray y es bastante corto y dulce:

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

¿Qué pasa si otras llamadas asíncronas que finalmente escriben en la base de datos están en la parte '// do stuff here'? ¿No lo encontrarán cerrado? – watery

Cuestiones relacionadas