接続

mongoose.connect()メソッドを使用してMongoDBに接続できます。

mongoose.connect('mongodb://127.0.0.1:27017/myapp');

これは、デフォルトポート(27017)でローカルに実行されているmyappデータベースに接続するために必要な最小限の設定です。ローカルのMongoDBデータベースの場合、localhostの代わりに127.0.0.1を使用することをお勧めします。これは、Node.js 18以降ではIPv6アドレスが優先されるため、多くのマシンでNode.jsがlocalhostをIPv6アドレス::1に解決し、mongodbインスタンスがipv6を有効にして実行されていない限り、Mongooseが接続できないためです。

uri には、さらにいくつかのパラメータを指定できます。

mongoose.connect('mongodb://username:password@host:port/database?options...');

詳細は、mongodb 接続文字列仕様を参照してください。

操作バッファリング

Mongooseでは、mongooseがMongoDBへの接続を確立するのを待たずに、モデルをすぐに使用できます。

mongoose.connect('mongodb://127.0.0.1:27017/myapp');
const MyModel = mongoose.model('Test', new Schema({ name: String }));
// Works
await MyModel.findOne();

これは、mongooseがモデル関数呼び出しを内部的にバッファリングするためです。このバッファリングは便利ですが、混乱の原因となることもよくあります。接続せずにモデルを使用しても、Mongooseはデフォルトでエラーをスローしません。

const MyModel = mongoose.model('Test', new Schema({ name: String }));
const promise = MyModel.findOne();

setTimeout(function() {
  mongoose.connect('mongodb://127.0.0.1:27017/myapp');
}, 60000);

// Will just hang until mongoose successfully connects
await promise;

バッファリングを無効にするには、スキーマのbufferCommandsオプションをオフにします。 bufferCommandsがオンで接続がハングしている場合は、bufferCommandsをオフにして、接続が正しく開かれていないかどうかを確認してください。bufferCommandsをグローバルに無効にすることもできます

mongoose.set('bufferCommands', false);

autoCreate オプションを使用する場合、バッファリングはMongooseがコレクションを作成するまで待機する役割も担います。バッファリングを無効にする場合は、autoCreateオプションも無効にし、createCollection()を使用してキャップ付きコレクションまたは照合順序付きコレクションを作成する必要があります。

const schema = new Schema({
  name: String
}, {
  capped: { size: 1024 },
  bufferCommands: false,
  autoCreate: false // disable `autoCreate` since `bufferCommands` is false
});

const Model = mongoose.model('Test', schema);
// Explicitly create the collection before using it
// so the collection is capped.
await Model.createCollection();

エラー処理

Mongoose接続で発生する可能性のあるエラーには、2つのクラスがあります。

  • 初期接続時のエラー:初期接続に失敗した場合、Mongooseは 'error' イベントを発行し、mongoose.connect() が返すpromiseは拒否されます。ただし、Mongooseは自動的に再接続を試みません。
  • 初期接続確立後のエラー:Mongooseは再接続を試み、 'error' イベントを発行します。

初期接続エラーを処理するには、.catch()またはasync/awaitでtry/catchを使用する必要があります。

mongoose.connect('mongodb://127.0.0.1:27017/test').
  catch(error => handleError(error));

// Or:
try {
  await mongoose.connect('mongodb://127.0.0.1:27017/test');
} catch (error) {
  handleError(error);
}

初期接続確立後のエラーを処理するには、接続でエラーイベントをリスンする必要があります。ただし、上記のように初期接続エラーを処理する必要があります。

mongoose.connection.on('error', err => {
  logError(err);
});

Mongooseは、MongoDBへの接続が失われた場合、必ずしも 'error' イベントを発行するとは限りません。MongooseがMongoDBから切断されたことを報告するには、disconnectedイベントをリスンする必要があります。

オプション

connectメソッドは、基盤となるMongoDBドライバーに渡されるoptionsオブジェクトも受け入れます。

mongoose.connect(uri, options);

オプションの完全なリストは、MongoClientOptions のMongoDB Node.jsドライバードキュメントにあります。Mongooseは、以下で説明するいくつかの例外を除いて、変更せずにオプションをドライバーに渡します。

  • bufferCommands - これはmongoose固有のオプション(MongoDBドライバーには渡されない)で、Mongooseのバッファリングメカニズムを無効にします
  • user/pass - 認証用のユーザー名とパスワード。これらのオプションはMongoose固有であり、MongoDBドライバーのauth.usernameおよびauth.passwordオプションと同等です。
  • autoIndex - デフォルトでは、mongooseは接続時にスキーマで定義されたインデックスを自動的に構築します。これは開発には最適ですが、インデックスの構築はパフォーマンスの低下を引き起こす可能性があるため、大規模な本番環境のデプロイメントには理想的ではありません。 autoIndexをfalseに設定すると、mongooseはこの接続に関連付けられたモデルのインデックスを自動的に構築しません。
  • dbName - 接続先のデータベースを指定し、接続文字列で指定されたデータベースをオーバーライドします。一部のmongodb+srv構文接続のように、接続文字列でデフォルトデータベースを指定できない場合に便利です。

Mongooseのチューニングに重要なオプションを以下に示します。

  • promiseLibrary - 基盤となるドライバーのプロミスライブラリを設定します。
  • maxPoolSize - MongoDBドライバーがこの接続のために開いたままにするソケットの最大数。デフォルトでは、maxPoolSizeは100です。MongoDBは一度に1つのソケットにつき1つの操作のみを許可しているため、高速なクエリの実行をブロックする低速なクエリがいくつかある場合は、これを増やす必要があるかもしれません。MongoDBとNode.jsの低速な処理を参照してください。接続制限に達している場合は、maxPoolSizeを減らす必要があるかもしれません。
  • minPoolSize - MongoDBドライバーがこの接続のために開いたままにするソケットの最小数。MongoDBドライバーは、しばらくの間非アクティブだったソケットを閉じることがあります。アプリが長期間アイドル状態になることが予想され、アクティビティが再開したときに低速な処理を回避するためにソケットを開いたままにする場合は、minPoolSizeを増やす必要があるかもしれません。
  • socketTimeoutMS - 初期接続後、MongoDBドライバーが非アクティブなソケットを強制終了するまでの待機時間。アクティビティがないか、長時間実行されている操作のために、ソケットが非アクティブになる場合があります。 socketTimeoutMSのデフォルトは0で、Node.jsは非アクティブなソケットをタイムアウトしません。このオプションは、MongoDBドライバーが正常に完了した後、Node.js socket#setTimeout()関数に渡されます。
  • family - IPv4またはIPv6のどちらを使用して接続するか。このオプションはNode.jsのdns.lookup()関数に渡されます。このオプションを指定しない場合、MongoDBドライバーは最初にIPv6を試行し、IPv6が失敗した場合はIPv4を試行します。 mongoose.connect(uri)の呼び出しに時間がかかる場合は、mongoose.connect(uri, { family: 4 })を試してください
  • authSource - userpassで認証するときに使用するデータベース。MongoDBでは、ユーザーはデータベースにスコープされます。予期しないログインエラーが発生した場合は、このオプションを設定する必要があるかもしれません。
  • serverSelectionTimeoutMS - MongoDBドライバーは、任意の操作を送信するサーバーを見つけようとし、serverSelectionTimeoutMSミリ秒間再試行し続けます。設定されていない場合、MongoDBドライバーはデフォルトで30000(30秒)を使用します。
  • heartbeatFrequencyMS - MongoDBドライバーは、接続の状態を確認するために、heartbeatFrequencyMSごとにハートビートを送信します。ハートビートはserverSelectionTimeoutMSの対象となるため、MongoDBドライバーはデフォルトで最大30秒間、失敗したハートビートを再試行します。Mongooseは、ハートビートが失敗した後でのみ'disconnected'イベントを発行するため、サーバーがダウンしてからMongooseが'disconnected'を発行するまでの時間を短縮するために、この設定を小さくする必要があるかもしれません。この設定を1000未満に設定しないことをお勧めします。ハートビートが多すぎると、パフォーマンスが低下する可能性があります。

serverSelectionTimeoutMS

serverSelectionTimeoutMSオプションは非常に重要です。MongoDB Node.jsドライバーがエラーを発生させる前に操作の再試行を試みる時間を制御します。これには、await mongoose.connect()などの初期接続、およびsave()find()などのMongoDBにリクエストを行う操作が含まれます。

デフォルトでは、serverSelectionTimeoutMSは30000(30秒)です。これは、たとえば、スタンドアロンMongoDBサーバーがダウンしているときにmongoose.connect()を呼び出すと、mongoose.connect()呼び出しは30秒後にのみエラーをスローすることを意味します。

// Throws an error "getaddrinfo ENOTFOUND doesnt.exist" after 30 seconds
await mongoose.connect('mongodb://doesnt.exist:27017/test');

同様に、初期接続後にスタンドアロンMongoDBサーバーがダウンした場合、MongoDBサーバーが再起動されない限り、find()またはsave()呼び出しは30秒後にエラーになります。

30秒は長いようですが、serverSelectionTimeoutMSは、レプリカセットフェイルオーバー中に中断が発生する可能性が低いことを意味します。レプリカセットプライマリを失った場合、レプリカセットの選出がserverSelectionTimeoutMSよりも短い時間で完了すると仮定すると、MongoDBノードドライバーは、レプリカセットの選出中に送信する操作が最終的に実行されることを保証します。

失敗した接続に関するフィードバックをより迅速に得るために、次のようにserverSelectionTimeoutMSを5000に減らすことができます。レプリカセットではなくスタンドアロンMongoDBサーバーを実行している場合、またはAWS Lambdaのようなサーバーレスランタイムを使用している場合を除き、serverSelectionTimeoutMSを減らすことはお勧めしません。

mongoose.connect(uri, {
  serverSelectionTimeoutMS: 5000
});

mongoose.connect()とクエリに対してserverSelectionTimeoutMSを個別に調整する方法はありません。クエリやその他の操作のためにserverSelectionTimeoutMSを削減したいが、それでもmongoose.connect()をより長く再試行したい場合は、forループまたはp-retryのようなツールを使用して、connect()呼び出しを自分で再試行する必要があります。

const serverSelectionTimeoutMS = 5000;

// Prints "Failed 0", "Failed 1", "Failed 2" and then throws an
// error. Exits after approximately 15 seconds.
for (let i = 0; i < 3; ++i) {
  try {
    await mongoose.connect('mongodb://doesnt.exist:27017/test', {
      serverSelectionTimeoutMS
    });
    break;
  } catch (err) {
    console.log('Failed', i);
    if (i >= 2) {
      throw err;
    }
  }
}

コールバック

connect()関数は、コールバックパラメータも受け入れ、promiseを返します。

mongoose.connect(uri, options, function(error) {
  // Check error in initial connection. There is no 2nd param to the callback.
});

// Or using promises
mongoose.connect(uri, options).then(
  () => { /** ready to use. The `mongoose.connect()` promise resolves to mongoose instance. */ },
  err => { /** handle initial connection error */ }
);

接続文字列オプション

URI のクエリ文字列部分に パラメータ としてドライバーオプションを指定することもできます。これは、MongoDB ドライバーに渡されるオプションにのみ適用されます。 bufferCommands のような Mongoose 固有のオプションをクエリ文字列で設定することはできません

mongoose.connect('mongodb://127.0.0.1:27017/test?socketTimeoutMS=1000&bufferCommands=false&authSource=otherdb');
// The above is equivalent to:
mongoose.connect('mongodb://127.0.0.1:27017/test', {
  socketTimeoutMS: 1000
  // Note that mongoose will **not** pull `bufferCommands` from the query string
});

クエリ文字列にオプションを配置することの欠点は、クエリ文字列のオプションが読みにくいことです。利点は、socketTimeoutMS など、個別のオプションではなく、URI という単一の構成オプションのみが必要になることです。ベストプラクティスは、開発環境と本番環境で異なる可能性のあるオプション(replicaSetssl など)を接続文字列に配置し、socketTimeoutMSmaxPoolSize などの一定であるべきオプションはオプションオブジェクトに配置することです。

MongoDB のドキュメントには、サポートされている接続文字列オプション の完全なリストがあります。以下は、ホスト名と認証情報に密接に関連付けられているため、接続文字列で設定すると便利なオプションの一部です。

  • authSource - userpassで認証するときに使用するデータベース。MongoDBでは、ユーザーはデータベースにスコープされます。予期しないログインエラーが発生した場合は、このオプションを設定する必要があるかもしれません。
  • family - IPv4またはIPv6のどちらを使用して接続するか。このオプションはNode.jsのdns.lookup()関数に渡されます。このオプションを指定しない場合、MongoDBドライバーは最初にIPv6を試行し、IPv6が失敗した場合はIPv4を試行します。 mongoose.connect(uri)の呼び出しに時間がかかる場合は、mongoose.connect(uri, { family: 4 })を試してください

接続イベント

接続は Node.js の EventEmitter クラス を継承し、MongoDB サーバーへの接続が失われるなど、接続に何かが発生した場合にイベントを発行します。以下は、接続が発行する可能性のあるイベントのリストです。

  • connecting: Mongoose が MongoDB サーバーへの初期接続を開始したときに発行されます。
  • connected: Mongoose が MongoDB サーバーへの初期接続に成功したとき、または接続が失われた後に Mongoose が再接続したときに発行されます。Mongoose が接続を失った場合、複数回発行される場合があります。
  • open: 'connected' 後に発行され、この接続のすべてのモデルで onOpen が実行されます。Mongoose が接続を失った場合、複数回発行される場合があります。
  • disconnecting: アプリケーションが MongoDB から切断するために Connection#close() を呼び出しました。これには、すべての接続で close() を呼び出す mongoose.disconnect() の呼び出しが含まれます。
  • disconnected: Mongoose が MongoDB サーバーへの接続を失ったときに発行されます。このイベントは、コードが明示的に接続を閉じているか、データベースサーバーがクラッシュしているか、ネットワーク接続の問題が原因である可能性があります。
  • close: Connection#close() が接続を正常に閉じた後に発行されます。 conn.close() を呼び出すと、「disconnected」イベントと「close」イベントの両方が発生します。
  • reconnected: Mongoose が MongoDB への接続を失い、正常に再接続した場合に発行されます。Mongoose は、データベースへの接続が失われた場合、自動的に再接続 を試みます。
  • error: 接続でエラーが発生した場合に発行されます。たとえば、不正なデータが原因の parseError や、16MB を超えるペイロードなどです。

単一の MongoDB サーバー("スタンドアロン")に接続している場合、Mongoose はスタンドアロンサーバーから切断されると disconnected を発行し、スタンドアロンに正常に接続すると connected を発行します。レプリカセット では、Mongoose はレプリカセットプライマリへの接続が失われると disconnected を発行し、レプリカセットプライマリへの再接続に成功すると connected を発行します。

mongoose.connect() を使用している場合は、上記のイベントをリッスンするために以下を使用できます。

mongoose.connection.on('connected', () => console.log('connected'));
mongoose.connection.on('open', () => console.log('open'));
mongoose.connection.on('disconnected', () => console.log('disconnected'));
mongoose.connection.on('reconnected', () => console.log('reconnected'));
mongoose.connection.on('disconnecting', () => console.log('disconnecting'));
mongoose.connection.on('close', () => console.log('close'));

mongoose.connect('mongodb://127.0.0.1:27017/mongoose_test');

mongoose.createConnection() を使用する場合は、代わりに以下を使用してください。

const conn = mongoose.createConnection('mongodb://127.0.0.1:27017/mongoose_test');

conn.on('connected', () => console.log('connected'));
conn.on('open', () => console.log('open'));
conn.on('disconnected', () => console.log('disconnected'));
conn.on('reconnected', () => console.log('reconnected'));
conn.on('disconnecting', () => console.log('disconnecting'));
conn.on('close', () => console.log('close'));

keepAliveに関する注意

Mongoose 5.2.0 より前では、"connection closed" エラーを防ぐために TCP キープアライブ を開始するには、keepAlive オプションを有効にする必要がありました。ただし、Mongoose 5.2.0 以降、keepAlive はデフォルトで true になっており、Mongoose 7.2.0 以降、keepAlive は非推奨となっています。Mongoose 接続から keepAlive および keepAliveInitialDelay オプションを削除してください。

レプリカセット接続

レプリカセットに接続するには、単一のホストではなく、接続するホストのカンマ区切りリストを渡します。

mongoose.connect('mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]' [, options]);

例えば

mongoose.connect('mongodb://user:pw@host1.com:27017,host2.com:27017,host3.com:27017/testdb');

単一ノードのレプリカセットに接続するには、replicaSet オプションを指定します。

mongoose.connect('mongodb://host1:port1/?replicaSet=rsName');

サーバー選択

基盤となる MongoDB ドライバーは、サーバー選択 と呼ばれるプロセスを使用して MongoDB に接続し、MongoDB に操作を送信します。MongoDB ドライバーが serverSelectionTimeoutMS 後に操作を送信するサーバーを見つけられない場合、以下のエラーが発生します。

MongoTimeoutError: Server selection timed out after 30000 ms

mongoose.connect()serverSelectionTimeoutMS オプションを使用してタイムアウトを構成できます。

mongoose.connect(uri, {
  serverSelectionTimeoutMS: 5000 // Timeout after 5s instead of 30s
});

MongoTimeoutError には、サーバー選択がタイムアウトした理由を説明する reason プロパティがあります。たとえば、間違ったパスワードでスタンドアロンサーバーに接続している場合、reason には「Authentication failed」エラーが含まれます。

const mongoose = require('mongoose');

const uri = 'mongodb+srv://username:badpw@cluster0-OMITTED.mongodb.net/' +
  'test?retryWrites=true&w=majority';
// Prints "MongoServerError: bad auth Authentication failed."
mongoose.connect(uri, {
  serverSelectionTimeoutMS: 5000
}).catch(err => console.log(err.reason));

レプリカセットホスト名

MongoDB レプリカセットは、各メンバーのドメイン名を確実に把握できることに依存しています。
Linux および OSX では、MongoDB サーバーは hostname コマンド の出力を使用して、レプリカセットに報告するドメイン名を特定します。これは、hostnamelocalhost として報告するマシンで実行されているリモート MongoDB レプリカセットに接続している場合、混乱を招くエラーを引き起こす可能性があります。

// Can get this error even if your connection string doesn't include
// `localhost` if `rs.conf()` reports that one replica set member has
// `localhost` as its host name.
MongooseServerSelectionError: connect ECONNREFUSED localhost:27017

同様のエラーが発生した場合は、mongo シェルを使用してレプリカセットに接続し、rs.conf() コマンドを実行して、各レプリカセットメンバーのホスト名を確認します。このページの手順に従って、レプリカセットメンバーのホスト名を変更 します。

MongooseServerSelectionErrorreason.servers プロパティをチェックして、MongoDB Node ドライバーがレプリカセットの状態をどのように認識しているかを確認することもできます。 reason.servers プロパティには、サーバー記述の マップ が含まれています。

if (err.name === 'MongooseServerSelectionError') {
  // Contains a Map describing the state of your replica set. For example:
  // Map(1) {
  //   'localhost:27017' => ServerDescription {
  //     address: 'localhost:27017',
  //     type: 'Unknown',
  //     ...
  //   }
  // }
  console.log(err.reason.servers);
}

マルチmongosサポート

シャーディングされたクラスターの高可用性のために、複数の mongos インスタンスに接続することもできます。mongoose 5.x では、複数の mongos に接続するために特別なオプションを渡す必要はありません

// Connect to 2 mongos servers
mongoose.connect('mongodb://mongosA:27501,mongosB:27501', cb);

複数接続

これまでは、Mongoose のデフォルト接続を使用して MongoDB に接続する方法を見てきました。Mongoose は、mongoose.connect() を呼び出すと、*デフォルト接続* を作成します。 mongoose.connection を使用してデフォルト接続にアクセスできます。

いくつかの理由で、MongoDB への複数の接続が必要になる場合があります。1つの理由は、複数のデータベースまたは複数の MongoDB クラスターがある場合です。もう1つの理由は、スロートレイン を回避するためです。 mongoose.createConnection() 関数は mongoose.connect() と同じ引数を取り、新しい接続を返します。

const conn = mongoose.createConnection('mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]', options);

この 接続 オブジェクトは、モデル を作成および取得するために使用されます。モデルは常に単一の接続にスコープされます。

const UserModel = conn.model('User', userSchema);

createConnection() 関数は、Promise ではなく接続インスタンスを返します。 await を使用して Mongoose が MongoDB に正常に接続することを確認するには、asPromise() 関数 を使用します。

// `asPromise()` returns a promise that resolves to the connection
// once the connection succeeds, or rejects if connection failed.
const conn = await mongoose.createConnection(connectionString).asPromise();

複数の接続を使用する場合は、モデルではなくスキーマをエクスポートする必要があります。ファイルからモデルをエクスポートすることを、*エクスポートモデルパターン* と呼びます。エクスポートモデルパターンは、1つの接続しか使用できないため、制限されています。

const userSchema = new Schema({ name: String, email: String });

// The alternative to the export model pattern is the export schema pattern.
module.exports = userSchema;

// Because if you export a model as shown below, the model will be scoped
// to Mongoose's default connection.
// module.exports = mongoose.model('User', userSchema);

エクスポートスキーマパターンを使用する場合でも、どこかでモデルを作成する必要があります。2つの一般的なパターンがあります。1つ目は、新しい接続をインスタンス化し、その接続にすべてのモデルを登録する関数を作成することです。このパターンでは、依存性注入または別の 制御の反転 (IOC) パターン を使用して接続を登録することもできます。

const mongoose = require('mongoose');

module.exports = function connectionFactory() {
  const conn = mongoose.createConnection(process.env.MONGODB_URI);

  conn.model('User', require('../schemas/user'));
  conn.model('PageView', require('../schemas/pageView'));

  return conn;
};

新しい接続を作成する関数をエクスポートするのが最も柔軟なパターンです。ただし、そのパターンでは、ルートハンドラーやビジネスロジックが存在する場所から接続にアクセスするのが難しくなる可能性があります。代替パターンは、接続をエクスポートし、ファイルのトップレベルスコープで接続にモデルを登録することです。以下に示すように。

// connections/index.js
const mongoose = require('mongoose');

const conn = mongoose.createConnection(process.env.MONGODB_URI);
conn.model('User', require('../schemas/user'));

module.exports = conn;

Web API バックエンドとモバイル API バックエンドに個別の接続を作成する場合、connections/web.jsconnections/mobile.js などのように、接続ごとに個別のファイルを作成できます。ビジネスロジックは、必要な接続を require() または import することができます。

コネクションプール

mongoose.connect または mongoose.createConnection で作成された各 connection は、すべてデフォルトの最大サイズが 100 の内部設定可能な接続プールによってサポートされています。接続オプションを使用してプールサイズを調整します。

// With object options
mongoose.createConnection(uri, { maxPoolSize: 10 });

// With connection string options
const uri = 'mongodb://127.0.0.1:27017/test?maxPoolSize=10';
mongoose.createConnection(uri);

接続プールサイズは、MongoDB は現在、ソケットあたり 1 つの操作しか処理できない ため、重要です。したがって、maxPoolSize は同時操作数の制限として機能します。

マルチテナント接続

Mongoose のコンテキストでは、マルチテナントアーキテクチャとは、通常、複数の異なるクライアントが単一の Mongoose アプリケーションを介して MongoDB と通信する場合を意味します。これは通常、各クライアントが単一の Mongoose アプリケーションを介してクエリを実行し、更新を実行しますが、同じ MongoDB クラスター内に個別の MongoDB データベースを持っていることを意味します。

Mongoose を使用したマルチテナンシーに関するこの記事 を読むことをお勧めします。マルチテナンシーの定義方法と、推奨されるパターンのより詳細な概要が記載されています。

Mongoose でのマルチテナンシーには、2 つのパターンをお勧めします。

  1. 1 つの接続プールを維持し、Connection.prototype.useDb() メソッド を使用してテナントを切り替えます。
  2. テナントごとに個別の接続プールを維持し、マップまたは POJO に接続を保存します。

以下は、パターン (1) の例です。少数のテナントがある場合、または個々のテナントのワークロードが軽い場合(約 1 秒あたり 1 リクエスト未満、すべてのリクエストのデータベース処理時間が 10 ミリ秒未満)、パターン (1) をお勧めします。パターン (1) は、接続プールが 1 つしかないため、実装と本番環境での管理がより簡単です。ただし、高負荷下では、スロートレイン により、一部のテナントの操作が他のテナントの操作を遅くするという問題が発生する可能性があります。

const express = require('express');
const mongoose = require('mongoose');

mongoose.connect('mongodb://127.0.0.1:27017/main');
mongoose.set('debug', true);

mongoose.model('User', mongoose.Schema({ name: String }));

const app = express();

app.get('/users/:tenantId', function(req, res) {
  const db = mongoose.connection.useDb(`tenant_${req.params.tenantId}`, {
    // `useCache` tells Mongoose to cache connections by database name, so
    // `mongoose.connection.useDb('foo', { useCache: true })` returns the
    // same reference each time.
    useCache: true
  });
  // Need to register models every time a new connection is created
  if (!db.models['User']) {
    db.model('User', mongoose.Schema({ name: String }));
  }
  console.log('Find users from', db.name);
  db.model('User').find().
    then(users => res.json({ users })).
    catch(err => res.status(500).json({ message: err.message }));
});

app.listen(3000);

以下はパターン (2) の例です。パターン (2) はより柔軟性があり、10,000 件以上のテナントと 1 秒あたり 1 件以上のリクエストがあるユースケースに適しています。各テナントが個別の接続プールを持つため、あるテナントの低速な操作が他のテナントに与える影響は最小限に抑えられます。ただし、このパターンは本番環境での実装と管理がより困難です。特に、MongoDB にはオープン接続数に制限があります。また、MongoDB Atlas にはオープン接続数に個別の制限があります。そのため、接続プール内のソケットの総数が MongoDB の制限を超えないようにする必要があります。

const express = require('express');
const mongoose = require('mongoose');

const tenantIdToConnection = {};

const app = express();

app.get('/users/:tenantId', function(req, res) {
  let initialConnection = Promise.resolve();
  const { tenantId } = req.params;
  if (!tenantIdToConnection[tenantId]) {
    tenantIdToConnection[tenantId] = mongoose.createConnection(`mongodb://127.0.0.1:27017/tenant_${tenantId}`);
    tenantIdToConnection[tenantId].model('User', mongoose.Schema({ name: String }));
    initialConnection = tenantIdToConnection[tenantId].asPromise();
  }
  const db = tenantIdToConnection[tenantId];
  initialConnection.
    then(() => db.model('User').find()).
    then(users => res.json({ users })).
    catch(err => res.status(500).json({ message: err.message }));
});

app.listen(3000);

次へ

接続について説明したので、次はモデルを見てみましょう。