¿Cómo administro las conexiones MongoDB en una aplicación web Node.js?

288

Estoy usando el controlador node-mongodb-native con MongoDB para escribir un sitio web.

Tengo algunas preguntas sobre cómo administrar las conexiones:

  1. ¿Es suficiente usar solo una conexión MongoDB para todas las solicitudes? ¿Hay algún problema de rendimiento? Si no, ¿puedo configurar una conexión global para usar en toda la aplicación?

  2. Si no, ¿es bueno si abro una nueva conexión cuando llega la solicitud y la cierro cuando la recibo? ¿Es costoso abrir y cerrar una conexión?

  3. ¿Debo usar un grupo de conexión global? Escuché que el controlador tiene un grupo de conexiones nativo. ¿Es una buena elección?

  4. Si uso un grupo de conexiones, ¿cuántas conexiones se deben usar?

  5. ¿Hay otras cosas que debería notar?

Viento libre
fuente
@ IonicãBizãu, lo siento, no he usado nodejs durante mucho tiempo y no lo he visto. Gracias por tu comentario ~
Freewind

Respuestas:

459

El confirmador principal de node-mongodb-native dice :

Abre do MongoClient.connect una vez cuando su aplicación se inicia y reutiliza el objeto db. No es un grupo de conexiones singleton cada .connect crea un nuevo grupo de conexiones.

Entonces, para responder a su pregunta directamente, reutilice el objeto db que resulta deMongoClient.connect() . Esto le permite agrupar y proporcionará un aumento notable de la velocidad en comparación con las conexiones de apertura / cierre en cada acción db.

Max
fuente
3
Enlace a MongoClient.connect () mongodb.github.io/node-mongodb-native/driver-articles/…
AndrewJM
44
Esta es la respuesta correcta. La respuesta aceptada es muy incorrecta, ya que dice abrir un grupo de conexiones para cada solicitud y luego cerrarla después de hacerlo. Terrible arquitectura.
Saransh Mohapatra
77
Esta es una respuesta correcta. ¡Dios mío, imagina que tengo que abrir y cerrar cada vez que hago algo, sería 350K por hora solo para mis insertos! Es como atacar mi propio servidor.
Maziyar
1
@Cracker: si tiene una aplicación express, puede guardar el objeto db en req.dbeste middleware: github.com/floatdrop/express-mongo-db
floatdrop
1
si hay varias bases de datos ... debería abrir una conexión para cada base de datos todas juntas. Si no, ¿está bien abrir y cerrar cuando sea necesario?
Aman Gupta
45

Abra una nueva conexión cuando se inicie la aplicación Node.js y reutilice el dbobjeto de conexión existente :

/server.js

import express from 'express';
import Promise from 'bluebird';
import logger from 'winston';
import { MongoClient } from 'mongodb';
import config from './config';
import usersRestApi from './api/users';

const app = express();

app.use('/api/users', usersRestApi);

app.get('/', (req, res) => {
  res.send('Hello World');
});

// Create a MongoDB connection pool and start the application
// after the database connection is ready
MongoClient.connect(config.database.url, { promiseLibrary: Promise }, (err, db) => {
  if (err) {
    logger.warn(`Failed to connect to the database. ${err.stack}`);
  }
  app.locals.db = db;
  app.listen(config.port, () => {
    logger.info(`Node.js app is listening at http://localhost:${config.port}`);
  });
});

/api/users.js

import { Router } from 'express';
import { ObjectID } from 'mongodb';

const router = new Router();

router.get('/:id', async (req, res, next) => {
  try {
    const db = req.app.locals.db;
    const id = new ObjectID(req.params.id);
    const user = await db.collection('user').findOne({ _id: id }, {
      email: 1,
      firstName: 1,
      lastName: 1
    });

    if (user) {
      user.id = req.params.id;
      res.send(user);
    } else {
      res.sendStatus(404);
    }
  } catch (err) {
    next(err);
  }
});

export default router;

Fuente: Cómo abrir conexiones de base de datos en una aplicación Node.js / Express

Konstantin Tarkus
fuente
1
Esto crea una conexión de base de datos ... si desea utilizar grupos, debe crear / cerrar en cada uso
amcdnl
15
Fuera de tema, este es el archivo NodeJS más extraño que he visto.
Aficionado
1
Nunca he oído hablar de app.locals antes, pero me alegro de que me las hayas presentado aquí
Z_z_Z
1
¡Me ayudo mucho! Cometo el error de crear / cerrar la conexión de base de datos para cada solicitud, el rendimiento de mi aplicación se redujo con esto.
Leandro Lima
18

Aquí hay un código que administrará sus conexiones MongoDB.

var MongoClient = require('mongodb').MongoClient;
var url = require("../config.json")["MongoDBURL"]

var option = {
  db:{
    numberOfRetries : 5
  },
  server: {
    auto_reconnect: true,
    poolSize : 40,
    socketOptions: {
        connectTimeoutMS: 500
    }
  },
  replSet: {},
  mongos: {}
};

function MongoPool(){}

var p_db;

function initPool(cb){
  MongoClient.connect(url, option, function(err, db) {
    if (err) throw err;

    p_db = db;
    if(cb && typeof(cb) == 'function')
        cb(p_db);
  });
  return MongoPool;
}

MongoPool.initPool = initPool;

function getInstance(cb){
  if(!p_db){
    initPool(cb)
  }
  else{
    if(cb && typeof(cb) == 'function')
      cb(p_db);
  }
}
MongoPool.getInstance = getInstance;

module.exports = MongoPool;

Cuando inicie el servidor, llame initPool

require("mongo-pool").initPool();

Luego, en cualquier otro módulo, puede hacer lo siguiente:

var MongoPool = require("mongo-pool");
MongoPool.getInstance(function (db){
    // Query your MongoDB database.
});

Esto se basa en la documentación de MongoDB . Mira esto.

Yaki Klein
fuente
3
Actualización desde 5.x: var option = {numberOfRetries: 5, auto_reconnect: true, poolSize: 40, connectTimeoutMS: 30000};
Blair
15

Administre grupos de conexión mongo en un solo módulo autónomo. Este enfoque proporciona dos beneficios. En primer lugar, mantiene su código modular y más fácil de probar. En segundo lugar, no está obligado a mezclar su conexión de base de datos en su objeto de solicitud, que NO es el lugar para un objeto de conexión de base de datos. (Dada la naturaleza de JavaScript, consideraría altamente peligroso mezclar cualquier cosa en un objeto construido por código de biblioteca). Entonces con eso solo necesita considerar un módulo que exporta dos métodos. connect = () => Promisey get = () => dbConnectionObject.

Con dicho módulo, primero puede conectarse a la base de datos

// runs in boot.js or what ever file your application starts with
const db = require('./myAwesomeDbModule');
db.connect()
    .then(() => console.log('database connected'))
    .then(() => bootMyApplication())
    .catch((e) => {
        console.error(e);
        // Always hard exit on a database connection error
        process.exit(1);
    });

Cuando está en vuelo, su aplicación simplemente puede llamar get()cuando necesita una conexión DB.

const db = require('./myAwesomeDbModule');
db.get().find(...)... // I have excluded code here to keep the example  simple

Si configura su módulo db de la misma manera que lo siguiente, no solo tendrá una manera de asegurarse de que su aplicación no se iniciará a menos que tenga una conexión de base de datos, sino que también tendrá una forma global de acceder a su grupo de conexiones de base de datos que generará un error si no tienes una conexión

// myAwesomeDbModule.js
let connection = null;

module.exports.connect = () => new Promise((resolve, reject) => {
    MongoClient.connect(url, option, function(err, db) {
        if (err) { reject(err); return; };
        resolve(db);
        connection = db;
    });
});

module.exports.get = () => {
    if(!connection) {
        throw new Error('Call connect first!');
    }

    return connection;
}
Stewart
fuente
muy útil, exactamente lo que estaba buscando!
agui
Mejor aún, podría deshacerse de la función connect () y hacer que la función get () verifique si la conexión es nula y llame a connect por usted si lo es. Have get () siempre devuelve una promesa. Así es como administro mi conexión y funciona muy bien. Este es un uso del patrón singleton.
java-addict301
@ java-addict301 Si bien ese enfoque proporciona una API más optimizada, tiene dos inconvenientes. La primera es que no hay una forma definida de verificar los errores de conexión. Tendrías que manejar esa línea en todas partes cuando llames a get. Me gusta fallar temprano con las conexiones de la base de datos y, en general, no dejaré que la aplicación arranque sin una conexión a la base de datos. El otro problema es el rendimiento. Debido a que no tiene una conexión activa, es posible que deba esperar un poco más en la primera llamada get () sobre la cual no tendrá control. Podría sesgar sus métricas de informes.
Stewart
1
@Stewart La forma en que estructuro las aplicaciones / servicios es típicamente recuperar una configuración de la base de datos al inicio. De esta manera, la aplicación no podrá iniciarse si la base de datos es inaccesible. Además, debido a que la primera solicitud siempre está en el inicio, no hay problema con las métricas con este diseño. También vuelva a lanzar una excepción de conexión con una vez en el singleton con un claro error que no puede conectarse. Dado que la aplicación necesita detectar errores de la base de datos al usar la conexión de todos modos, esto no conduce a ningún manejo adicional en línea.
java-addict301
2
Hola @Ayan Es importante tener en cuenta que aquí, cuando llamamos, get()obtenemos un grupo de conexiones, no una sola conexión. Un grupo de conexiones, como su nombre lo indica, es una colección lógica de conexiones de bases de datos. Si no hay conexiones en el grupo, el controlador intentará abrir una. Una vez que esa conexión está abierta, se usa y se devuelve al grupo. La próxima vez que se acceda al grupo, esta conexión puede reutilizarse. Lo bueno aquí es que el grupo administrará nuestras conexiones por nosotros, por lo que si se corta una conexión, es posible que nunca lo sepamos, ya que el grupo abrirá una nueva para nosotros.
Stewart
11

Si tiene Express.js, puede usar express-mongo-db para almacenar en caché y compartir la conexión MongoDB entre solicitudes sin un grupo (ya que la respuesta aceptada dice que es la forma correcta de compartir la conexión).

Si no, puede ver su código fuente y usarlo en otro marco.

flotador
fuente
6

He estado usando el grupo genérico con conexiones redis en mi aplicación, lo recomiendo encarecidamente. Es genérico y definitivamente sé que funciona con mysql, así que no creo que tengas ningún problema con él y con mongo

https://github.com/coopernurse/node-pool

ControlAltDel
fuente
Mongo ya agrupa las conexiones en el controlador, sin embargo, he asignado mis conexiones mongo en una interfaz que coincide con el grupo de nodos, de esta manera todas mis conexiones siguen el mismo patrón, aunque en el caso de mongo, la limpieza no en realidad desencadenar cualquier cosa.
Rastreador1
4

Debe crear una conexión como servicio y luego reutilizarla cuando sea necesario.

// db.service.js
import { MongoClient } from "mongodb";
import database from "../config/database";

const dbService = {
  db: undefined,
  connect: callback => {
    MongoClient.connect(database.uri, function(err, data) {
      if (err) {
        MongoClient.close();
        callback(err);
      }
      dbService.db = data;
      console.log("Connected to database");
      callback(null);
    });
  }
};

export default dbService;

mi muestra de App.js

// App Start
dbService.connect(err => {
  if (err) {
    console.log("Error: ", err);
    process.exit(1);
  }

  server.listen(config.port, () => {
    console.log(`Api runnning at ${config.port}`);
  });
});

y úsalo donde quieras con

import dbService from "db.service.js"
const db = dbService.db
TheAlemazing
fuente
1
Si mongo no pudo conectarse, MongoClient.close () da un error. Pero una buena solución para el problema original.
Himanshu
3

http://mongoosejs.com/docs/api.html

Echa un vistazo a la fuente de Mangosta. Abren una conexión y la vinculan a un objeto Modelo, de modo que cuando se requiere el Objeto modelo, se realiza una conexión a la base de datos. El conductor se encarga de la agrupación de conexiones.

srquinn
fuente
Original es para conector nativo mongodb .
CodeFinity
2

He implementado el siguiente código en mi proyecto para implementar la agrupación de conexiones en mi código para que cree una conexión mínima en mi proyecto y reutilice la conexión disponible

/* Mongo.js*/

var MongoClient = require('mongodb').MongoClient;
var url = "mongodb://localhost:27017/yourdatabasename"; 
var assert = require('assert');

var connection=[];
// Create the database connection
establishConnection = function(callback){

                MongoClient.connect(url, { poolSize: 10 },function(err, db) {
                    assert.equal(null, err);

                        connection = db
                        if(typeof callback === 'function' && callback())
                            callback(connection)

                    }

                )



}

function getconnection(){
    return connection
}

module.exports = {

    establishConnection:establishConnection,
    getconnection:getconnection
}

/*app.js*/
// establish one connection with all other routes will use.
var db = require('./routes/mongo')

db.establishConnection();

//you can also call with callback if you wanna create any collection at starting
/*
db.establishConnection(function(conn){
  conn.createCollection("collectionName", function(err, res) {
    if (err) throw err;
    console.log("Collection created!");
  });
};
*/

// anyother route.js

var db = require('./mongo')

router.get('/', function(req, res, next) {
    var connection = db.getconnection()
    res.send("Hello");

});
Aditya Parmar
fuente
1

El mejor enfoque para implementar la agrupación de conexiones es crear una variable de matriz global que contenga el nombre db con el objeto de conexión devuelto por MongoClient y luego reutilizar esa conexión cada vez que necesite ponerse en contacto con la base de datos.

  1. En su Server.js defina var global.dbconnections = [];

  2. Cree una conexión de nomenclatura de servicios. Tendrá 2 métodos getConnection y createConnection. Entonces, cuando el usuario llame a getConnection (), encontrará detalles en la variable de conexión global y devolverá los detalles de la conexión si ya existe; llamará a createConnection () y devolverá los Detalles de la conexión.

  3. Llame a este servicio usando db_name y devolverá el objeto de conexión si ya lo tiene, creará una nueva conexión y se la devolverá.

Espero eso ayude :)

Aquí está el código connectionService.js:

var mongo = require('mongoskin');
var mongodb = require('mongodb');
var Q = require('q');
var service = {};
service.getConnection = getConnection ;
module.exports = service;

function getConnection(appDB){
    var deferred = Q.defer();
    var connectionDetails=global.dbconnections.find(item=>item.appDB==appDB)

    if(connectionDetails){deferred.resolve(connectionDetails.connection);
    }else{createConnection(appDB).then(function(connectionDetails){
            deferred.resolve(connectionDetails);})
    }
    return deferred.promise;
}

function createConnection(appDB){
    var deferred = Q.defer();
    mongodb.MongoClient.connect(connectionServer + appDB, (err,database)=> 
    {
        if(err) deferred.reject(err.name + ': ' + err.message);
        global.dbconnections.push({appDB: appDB,  connection: database});
        deferred.resolve(database);
    })
     return deferred.promise;
} 
RRPatel
fuente
0

mongodb.com -> nuevo proyecto -> nuevo clúster -> nueva colección -> conectar -> dirección IP: 0.0.0.0/0 y db cred -> conecte su aplicación -> copie la cadena de conexión y pegue el archivo .env de su nodo aplicación y asegúrese de reemplazar "" con la contraseña real para el usuario y también reemplazar "/ prueba" con su nombre de base de datos

crear nuevo archivo .env

CONNECTIONSTRING=x --> const client = new MongoClient(CONNECTIONSTRING) 
PORT=8080 
JWTSECRET=mysuper456secret123phrase
anoNewb
fuente
0

Si usa express, existe otro método más directo, que es utilizar la función integrada de Express para compartir datos entre rutas y módulos dentro de su aplicación. Hay un objeto llamado app.locals. Podemos adjuntarle propiedades y acceder desde nuestras rutas. Para usarlo, crea una instancia de tu conexión mongo en tu archivo app.js.

var app = express();

MongoClient.connect('mongodb://localhost:27017/')
.then(client =>{
  const db = client.db('your-db');
  const collection = db.collection('your-collection');
  app.locals.collection = collection;
});
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              // view engine setup
app.set('views', path.join(__dirname, 'views'));

Ahora puede acceder a esta conexión de base de datos, o incluso a cualquier otro dato que desee compartir alrededor de los módulos de su aplicación, dentro de sus rutas, req.app.localscomo se muestra a continuación, sin la necesidad de crear y requerir módulos adicionales.

app.get('/', (req, res) => {
  const collection = req.app.locals.collection;
  collection.find({}).toArray()
  .then(response => res.status(200).json(response))
  .catch(error => console.error(error));
});

Este método garantiza que tenga una conexión de base de datos abierta durante la duración de su aplicación, a menos que elija cerrarla en cualquier momento. Es de fácil acceso req.app.locals.your-collectiony no requiere la creación de ningún módulo adicional.

Hoppo
fuente