Node.js WebアプリケーションでMongoDB接続を管理するにはどうすればよいですか?


288

私は、MongoDBでnode-mongodb-nativeドライバーを使用してWebサイトを作成しています。

接続を管理する方法についていくつか質問があります。

  1. すべてのリクエストに対して1つのMongoDB接続を使用するだけで十分ですか?パフォーマンスの問題はありますか?そうでない場合、アプリケーション全体で使用するグローバル接続をセットアップできますか?

  2. そうでない場合、リクエストが到着したときに新しい接続を開き、リクエストを処理したときにそれを閉じるとよいですか?接続を開いたり閉じたりするには費用がかかりますか?

  3. グローバル接続プールを使用する必要がありますか?ドライバーにネイティブ接続プールがあると聞きました。それは良い選択ですか?

  4. 接続プールを使用する場合、いくつの接続を使用する必要がありますか?

  5. 他に気をつけるべきことはありますか?


@IonicãBizãu申し訳ありませんが、nodejsを長い間使用していないので、表示していません。あなたのコメントをありがとう〜
Freewind

回答:


459

node-mongodb-nativeの主なコミッターは言う

アプリの起動時にdo MongoClient.connectを1回開き、dbオブジェクトを再利用します。シングルトン接続プールではなく、各.connectが新しい接続プールを作成します。

したがって、質問に直接回答するには、の結果であるdbオブジェクトを再利用しますMongoClient.connect()。これによりプーリングが可能になり、各dbアクションで接続を開いたり閉じたりする場合と比較して、速度が著しく向上します。


3
MongoClient.connectへのリンク()mongodb.github.io/node-mongodb-native/driver-articles/...
AndrewJM

4
これが正解です。受け入れられた回答は、各要求に対して接続プールを開いてから、閉じるように指示しているため、非常に間違っています。ひどいアーキテクチャ。
サランシュモハパトラ2013

7
これが正解です。私の神は私が何かをするたびに開閉しなければならないことを想像します。それは私の挿入物だけで1時間あたり350Kになるでしょう!それは自分のサーバーを攻撃するようなものです。
Maziyar 2013

1
@Cracker:高速アプリケーションがある場合、次のreq.dbミドルウェアを使用してdbオブジェクトを保存できます:github.com/floatdrop/express-mongo-db
floatdrop

1
複数のデータベースがある場合、各データベースの接続をすべて一緒に開く必要があります。そうでない場合、必要に応じて開いたり閉じたりしても大丈夫ですか?
Aman Gupta 2016年

45

Node.jsアプリケーションの起動時に新しい接続を開き、既存のdb接続オブジェクトを再利用します。

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

ソース:Node.js / Expressアプリでデータベース接続を開く方法


1
これにより、1つのデータベース接続が作成されます...プールを使用する場合は、使用するたびに作成/クローズする必要があります
amcdnl

15
オフトピック、これは私が今まで見た中で最も奇妙なNodeJSファイルです。
ホビーイスト

1
前app.localsのことを聞いていないが、私はあなたがここではそれらを紹介してくれてうれしいん
Z_z_Z

1
とても助かりました!リクエストごとにDB接続を作成/閉じるのを間違えましたが、これによりアプリのパフォーマンスが低下しました。
Leandro Lima

18

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;

サーバーを起動したら、次を呼び出します initPool

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

次に、他のモジュールで次のことを実行できます。

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

これは、MongoDBのドキュメントに基づいています。それを見てください。


3
5.x以降の更新:var option = {numberOfRetries:5、auto_reconnect:true、poolSize:40、connectTimeoutMS:30000};
ブレア

15

単一の自己完結型モジュールでmongo接続プールを管理します。このアプローチには2つの利点があります。まず、コードをモジュール化し、テストを容易にします。次に、データベース接続オブジェクトの場所ではないリクエストオブジェクトでデータベース接続を混在させる必要はありません。(JavaScriptの性質を考えると、ライブラリコードによって構築されたオブジェクトに何かを混在させることは非常に危険だと思います)。したがって、2つのメソッドをエクスポートするモジュールを検討するだけで済みます。connect = () => Promiseget = () => dbConnectionObject

このようなモジュールを使用すると、最初にデータベースに接続できます

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

処理中get()は、DB接続が必要なときにアプリで呼び出すことができます。

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

以下と同じ方法でdbモジュールを設定すると、データベース接続がない限り、アプリケーションが起動しないようにする方法だけでなく、エラーになるデータベース接続プールにアクセスするグローバルな方法も利用できます。接続がない場合。

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

非常に便利で、まさに私が探していたものです!
agui 2017年

さらに良いのは、connect()関数を削除し、get()関数で接続がnullかどうかを確認し、nullの場合はconnectを呼び出すことです。get()が常にpromiseを返すようにします。これは私の接続を管理する方法であり、うまく機能します。これはシングルトンパターンの使用です。
java-addict301 2017年

@ java-addict301そのアプローチはより合理化されたAPIを提供しますが、2つの欠点があります。1つ目は、接続エラーをチェックする方法が定義されていないことです。getを呼び出すときはいつでも、どこでもインラインで処理する必要があります。私はデータベース接続で早期に失敗するのが好きで、通常、データベースへの接続なしでアプリを起動させません。もう1つの問題はスループットです。アクティブな接続がないため、制御できない最初のget()呼び出しでもう少し待つ必要がある場合があります。レポートの指標を歪める可能性があります。
スチュワート

1
@Stewartアプリケーション/サービスを構成する方法は、通常、起動時にデータベースから構成を取得することです。このようにして、データベースにアクセスできない場合、アプリケーションの起動に失敗します。また、最初のリクエストは常に起動時に行われるため、このデザインのメトリックには問題がありません。また、接続できないという明確なエラーが発生したシングルトンの1か所に接続例外を再スローします。とにかく接続を使用する場合、アプリはデータベースエラーをキャッチする必要があるため、これによって余分なインライン処理が発生することはありません。
java-addict301

2
@Ayanさん、こんにちは。ここで、呼び出し時にget()単一の接続ではなく接続プールを取得していることに注意してください。接続プールは、その名前が示すように、データベース接続の論理的なコレクションです。プールに接続がない場合、ドライバーは接続を開こうとします。その接続が開かれると、それが使用され、プールに返されます。次にプールにアクセスしたときに、この接続は再利用される可能性があります。ここでの良い点は、プールが接続を管理するので、接続がドロップされた場合、プールが新しい接続を開くため、接続が失われることはありません。
スチュワート

11

Express.jsがある場合は、プールなしでリクエスト間でMongoDB接続をキャッシュおよび共有するためにexpress-mongo-dbを使用できます(受け入れられた回答は接続を共有する正しい方法であるとされているため)。

そうでない場合-そのソースコードを見て、別のフレームワークで使用できます。


6

私は自分のアプリでredis接続でgeneric-poolを使用しています-強くお勧めします。その一般的で、mysqlで動作することは間違いなくわかっているので、それとmongoで問題が発生することはないと思います

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


Mongoは既にドライバーで接続プーリングを行っていますが、mongo接続をノードプールと一致するインターフェースにマッピングしました。これにより、すべての接続は同じパターンに従いますが、mongoの場合、クリーンアップは行われません実際には何でもトリガーします。
Tracker1 2014

4

サービスとして接続を作成し、必要に応じて再利用する必要があります。

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

私の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}`);
  });
});

どこでも好きな場所で使用できます

import dbService from "db.service.js"
const db = dbService.db

1
mongoが接続できなかった場合、MongoClient.close()はエラーを返します。しかし、元の問題の良い解決策です。
ヒマンシュ

3

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

Mongooseのソースを確認してください。それらは接続を開き、それをモデルオブジェクトにバインドするので、モデルオブジェクトが必要な場合、DBに接続されます。ドライバは接続プーリングを処理します。


オリジナルはネイティブ mongodbコネクタ用です。
CodeFinity

2

プロジェクトに以下のコードを実装して、接続プーリングをコードに実装しました。これにより、プロジェクトに最小限の接続が作成され、使用可能な接続が再利用されます。

/* 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");

});

1

接続プールを実装する最良の方法は、MongoClientから返された接続オブジェクトを使用してデータベース名を保持する1つのグローバル配列変数を作成し、データベースに接続する必要があるときはいつでもその接続を再利用することです。

  1. Server.jsでvar global.dbconnections = [];を定義します。

  2. connectionService.jsという名前のサービスを作成します。getConnectionとcreateConnectionの2つのメソッドがあります。したがって、ユーザーがgetConnection()を呼び出すと、グローバル接続変数で詳細が検索され、すでに存在する場合は接続の詳細が返されます。それ以外の場合は、createConnection()を呼び出して接続の詳細を返します。

  3. db_nameを使用してこのサービスを呼び出すと、すでに他にある場合は接続オブジェクトが返され、新しい接続が作成されて返されます。

それが役に立てば幸い :)

次に、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;
} 

0

mongodb.com- >新しいプロジェクト->新しいクラスター->新しいコレクション->接続-> IPアドレス:0.0.0.0/0&db cred->アプリケーションを接続->接続文字列をコピーしてノードの.envファイルに貼り付けアプリの「」をユーザーの実際のパスワードに置き換え、「/ test」を実際のデータベース名に置き換えてください

新しいファイル.envを作成する

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

0

Expressを使用する場合、もう1つの簡単な方法があります。それは、Expressの組み込み機能を利用して、アプリ内のルートとモジュール間でデータを共有することです。app.localsというオブジェクトがあります。プロパティを添付して、ルートの内部からアクセスできます。これを使用するには、mongo接続を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'));

このデータベース接続、または実際にアプリのモジュールの周りで共有したい他のデータは、req.app.locals追加のモジュールを作成して必要とすることなく、以下のようにルート内でアクセスできます。

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

この方法により、アプリをいつでも閉じることを選択しない限り、アプリの実行中はデータベース接続が開いたままになります。で簡単にアクセスでき、req.app.locals.your-collection追加のモジュールを作成する必要はありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.