Como gerencio conexões MongoDB em um aplicativo da web Node.js.

288

Estou usando o driver nativo do node-mongodb com o MongoDB para escrever um site.

Tenho algumas perguntas sobre como gerenciar conexões:

  1. É suficiente usar apenas uma conexão MongoDB para todas as solicitações? Existem problemas de desempenho? Caso contrário, posso configurar uma conexão global para usar em todo o aplicativo?

  2. Caso contrário, é bom abrir uma nova conexão quando a solicitação chegar e fechá-la quando a solicitação foi processada? É caro abrir e fechar uma conexão?

  3. Devo usar um pool de conexão global? Ouvi dizer que o driver tem um pool de conexão nativo. É uma boa escolha?

  4. Se eu usar um pool de conexões, quantas conexões devem ser usadas?

  5. Há outras coisas que devo observar?

Vento livre
fonte
@ IonicãBizãu, desculpe, eu não uso o nodejs há muito tempo que não o vejo. Obrigado pelo seu comentário ~
Freewind

Respostas:

459

O committer primário para node-mongodb-native diz :

Você abre o MongoClient.connect uma vez quando o aplicativo é inicializado e reutiliza o objeto db. Não é um conjunto de conexões singleton cada .connect cria um novo conjunto de conexões.

Portanto, para responder sua pergunta diretamente, reutilize o objeto db resultanteMongoClient.connect() . Isso fornece pool e proporcionará um aumento notável da velocidade em comparação com as conexões de abertura / fechamento em cada ação de banco de dados.

Máx.
fonte
3
Link para MongoClient.connect () mongodb.github.io/node-mongodb-native/driver-articles/...
AndrewJM
4
Essa é a resposta correta. A resposta aceita está muito errada, pois diz para abrir um pool de conexão para cada solicitação e depois fechá-lo depois de fazer isso. Arquitetura terrível.
Saransh Mohapatra
7
Esta é uma resposta certa. Meu deus imagina que eu tenho que abrir e fechar cada vez que faço algo que seria 350K por hora apenas para minhas inserções! É como atacar meu próprio servidor.
Maziyar
1
@Cracker: Se tiver pedido expresso, você pode salvar objeto db para req.dbcom este middleware: github.com/floatdrop/express-mongo-db
floatdrop
1
se houver vários bancos de dados ... devo abrir uma conexão para cada banco de dados todos juntos. Caso contrário, é possível abrir e fechar quando necessário?
Aman Gupta
45

Abra uma nova conexão quando o aplicativo Node.js for iniciado e reutilize o dbobjeto de conexão 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;

Origem: Como abrir conexões de banco de dados em um aplicativo Node.js / Express

Konstantin Tarkus
fonte
1
Isso cria uma conexão com o banco de dados ... se você deseja utilizar pools, precisa criar / fechar a cada uso #
amcdnl
15
Fora do tópico, este é o arquivo NodeJS mais estranho que eu já vi.
Hobbyist
1
Nunca ouvi falar app.locals antes, mas eu estou feliz que você me apresentou a eles aqui
Z_z_Z
1
Me ajudou muito! Cometo o erro de criar / fechar a conexão com o banco de dados para cada solicitação, o desempenho do meu aplicativo caiu com isso.
Leandro Lima
18

Aqui está um código que gerenciará suas conexões do 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;

Quando você inicia o servidor, chame initPool

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

Em qualquer outro módulo, você pode fazer o seguinte:

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

Isso é baseado na documentação do MongoDB . Dê uma olhada nisto.

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

Gerenciar conjuntos de conexões mongo em um único módulo independente. Essa abordagem oferece dois benefícios. Em primeiro lugar, mantém seu código modular e mais fácil de testar. Em segundo lugar, você não é obrigado a misturar sua conexão com o banco de dados no objeto de solicitação, que NÃO é o local para um objeto de conexão com o banco de dados. (Dada a natureza do JavaScript, consideraria altamente perigoso misturar qualquer coisa a um objeto construído pelo código da biblioteca). Então, com isso, você só precisa considerar um módulo que exporte dois métodos. connect = () => Promisee get = () => dbConnectionObject.

Com esse módulo, você pode primeiro conectar-se ao banco de dados

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

Quando em vôo, seu aplicativo pode simplesmente ligar get()quando precisar de uma conexão com o banco de dados.

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

Se você configurar seu módulo db da mesma maneira que a seguir, não apenas terá uma maneira de garantir que seu aplicativo não inicialize, a menos que você tenha uma conexão com o banco de dados, mas também uma maneira global de acessar seu pool de conexão com o banco de dados que irá se você não tiver uma conexão.

// 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
fonte
muito útil, exatamente o que eu estava procurando!
agui
Melhor ainda, você pode se livrar da função connect () e fazer com que a função get () verifique se a conexão é nula e chame connect para você, se for. Sempre receba get () uma promessa. É assim que gerencio minha conexão e ela funciona muito bem. Este é um uso do padrão singleton.
Java-addict301
@ java-addict301 Embora essa abordagem forneça uma API mais simplificada, ela tem duas desvantagens. A primeira é que não há uma maneira definida de verificar se há erros de conexão. Você precisaria lidar com isso em qualquer lugar sempre que telefonar para get. Gosto de falhar cedo nas conexões com o banco de dados e geralmente não deixo o aplicativo inicializar sem uma conexão com o banco de dados. A outra questão é a taxa de transferência. Como você não possui uma conexão ativa, pode ser necessário esperar um pouco mais na primeira chamada get () sobre a qual você não terá controle. Pode distorcer suas métricas de relatórios.
Stewart
1
@ Stewart A maneira como estruturo aplicativos / serviços é normalmente recuperar uma configuração do banco de dados na inicialização. Dessa maneira, o aplicativo falhará ao iniciar se o banco de dados estiver inacessível. Além disso, como a primeira solicitação está sempre na inicialização, não há problema com as métricas desse design. Também repita novamente uma exceção de conexão no lugar único no singleton com um erro claro que não pode se conectar. Como o aplicativo precisa detectar erros no banco de dados ao usar a conexão de qualquer maneira, isso não leva a nenhum tratamento inline extra.
java-addict301
2
Oi @Ayan. É importante observar que aqui, quando ligamos get(), estamos obtendo um pool de conexões e não uma única conexão. Um conjunto de conexões, como o próprio nome indica, é uma coleção lógica de conexões com o banco de dados. Se não houver conexões no pool, o driver tentará abrir uma. Depois que a conexão é aberta, ela é usada e retornada ao pool. A próxima vez que o pool for acessado, essa conexão poderá ser reutilizada. O bom aqui é que a piscina gerenciará nossas conexões para nós. Se uma conexão for interrompida, talvez nunca saibamos, pois a piscina abrirá uma nova para nós.
Stewart
11

Se você possui o Express.js, pode usar o express-mongo-db para armazenar em cache e compartilhar a conexão MongoDB entre solicitações sem pool (uma vez que a resposta aceita diz que é o caminho certo para compartilhar a conexão).

Caso contrário - você pode olhar para o código fonte e usá-lo em outra estrutura.

flutuar
fonte
6

Eu tenho usado um pool genérico com conexões redis no meu aplicativo - eu recomendo. É genérico e eu definitivamente sei que funciona com mysql, então eu não acho que você terá problemas com ele e o mongo

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

ControlAltDel
fonte
O Mongo já faz o pool de conexões no driver. No entanto, mapeei minhas conexões do mongo em uma interface que corresponde ao pool de nós, dessa forma todas as minhas conexões seguem o mesmo padrão, embora no caso do mongo, a limpeza não ocorra. realmente acionar qualquer coisa.
precisa saber é o seguinte
4

Você deve criar uma conexão como serviço e depois reutilizá-la quando necessário.

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

minha amostra do 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}`);
  });
});

e use-o onde quiser com

import dbService from "db.service.js"
const db = dbService.db
TheAlemazing
fonte
1
Se o mongo não pôde se conectar, o MongoClient.close () apresentará um erro. Mas uma boa solução para o problema original.
precisa
3

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

Confira a fonte do Mongoose. Eles abrem uma conexão e a vinculam a um objeto Model, portanto, quando o Objeto do modelo é necessário, é feita uma conexão com o DB. O motorista cuida do pool de conexões.

srquinn
fonte
Original é para conector nativo mongodb .
CodeFinity
2

Eu implementei o código abaixo no meu projeto para implementar o pool de conexões no meu código, para que ele crie uma conexão mínima no meu projeto e reutilize a conexão disponível

/* 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
fonte
1

A melhor abordagem para implementar o pool de conexões é criar uma variável de matriz global que mantenha o nome db com o objeto de conexão retornado pelo MongoClient e reutilize essa conexão sempre que precisar entrar em contato com o Banco de Dados.

  1. No seu Server.js, defina var global.dbconnections = [];

  2. Crie um serviço nomeando connectionService.js. Ele terá 2 métodos getConnection e createConnection. Portanto, quando o usuário chamará getConnection (), encontrará detalhes na variável de conexão global e retornará os detalhes da conexão, se já existir, caso contrário, chamará createConnection () e retornará Detalhes da conexão.

  3. Chame este serviço usando db_name e ele retornará o objeto de conexão, se já tiver mais, ele criará uma nova conexão e retornará para você.

Espero que ajude :)

Aqui está o 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
fonte
0

mongodb.com -> novo projeto -> novo cluster -> nova coleção -> conectar -> endereço IP: 0.0.0.0/0 & db cred -> conecte seu aplicativo -> copie a cadeia de conexão e cole no arquivo .env do seu nó aplicativo e certifique-se de substituir "" pela senha real do usuário e também substituir "/ test" pelo seu nome db

crie um novo arquivo .env

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

Se estiver usando o express, existe outro método mais direto, que é utilizar o recurso incorporado do Express para compartilhar dados entre rotas e módulos no seu aplicativo. Há um objeto chamado app.locals. Podemos anexar propriedades a ele e acessá-lo de dentro de nossas rotas. Para usá-lo, instancie sua conexão mongo no arquivo 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'));

Agora, essa conexão com o banco de dados ou qualquer outro dado que você deseja compartilhar em torno dos módulos do seu aplicativo pode ser acessado dentro de suas rotas, req.app.localsconforme abaixo, sem a necessidade de criar e exigir módulos adicionais.

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

Esse método garante que você tenha uma conexão com o banco de dados aberta durante a duração do seu aplicativo, a menos que opte por fechá-la a qualquer momento. É facilmente acessível req.app.locals.your-collectione não requer a criação de módulos adicionais.

Hoppo
fonte