MongoDBの統一トポロジー(useUnifiedTopology)について
最近のMongoDBドライバーはエラーが出る
(node:8746) [MONGODB DRIVER] Warning: Current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.
で、その新トポロジーの説明はこれ。
すごくザックリ内容を紹介すると
- 接続中(connected)という状態とはなんぞや?
- 実際のネットワークが接続状態という事に意味はあるのか?
- 別にになっていなくても処理可能な状態を保っていれば良いよね?
- だからisConnectedも将来廃止するよ。
- 直ちに処理出来ない状態でもドライバー内でなんとか辻褄取るよ
- でも失敗したら30秒位でエラー返すよ
なので、useUnifiedTopology と autoReconnect を両方指定すると怒られる。
(node:8962) [MONGODB DRIVER] DeprecationWarning: The option `autoReconnect` is incompatible with the unified topology, please read more by visiting http://bit.ly/2D8WfT6
新しいトポロジーでは、利用者側は接続状態を気にしなくて良い事になっているからね。
ザックリ図解
現行ReplicaSet
readPreferenceに合致するコネクションを割り当てる事しかしない。
一度コネクションを取得した後は素通し。
コネクションプール内のコネクションが全部エラーで破棄されるまでエラーが続く。
新統一トポロジー
1層咬んでいるのでエラーにならない所を勝手に探してくれる。
全部ダメならタイムアウトserverSelectionTimeoutMS までリトライする。
実際の挙動
createConnection
現行ReplicaSet
require('mongoose').createConnection('mongodb://localhost:27017/foo', { autoReconnect: true, readPreference: 'secondaryPreferred' })
直ちにエラーが帰る
MongoNetworkError: failed to connect to server [localhost:27017] on first connect [Error: connect ECONNREFUSED 127.0.0.1:27017
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1161:16)
at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
name: 'MongoNetworkError'
}]
新統一トポロジー
require('mongoose').createConnection('mongodb://localhost:27017/foo', { useUnifiedTopology: true, readPreference: 'secondaryPreferred', serverSelectionTimeoutMS: 30000, })
約30秒後にこうなる
> Uncaught MongooseServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017
at NativeConnection.Connection.openUri (/****/node_modules/mongoose/lib/connection.js:847:32)
at Mongoose.createConnection (/****/node_modules/mongoose/lib/index.js:291:17) {
reason: TopologyDescription {
type: 'Unknown',
setName: null,
maxSetVersion: null,
maxElectionId: null,
servers: Map(1) { 'localhost:27017' => [ServerDescription] },
stale: false,
compatible: true,
compatibilityError: null,
logicalSessionTimeoutMinutes: null,
heartbeatFrequencyMS: 10000,
localThresholdMS: 15,
commonWireVersion: null
}
}
fetch
図解の通りに動くので、動作確認のコード例だけ。
このコードを動かしてレプリカセットのノードを落としたり上げたりすればいい。
新統一トポロジーの方は全ノードを落として、再起動せずにタイムアウトに抵触するまではエラーが出ない。
現行ReplicaSet
let conn = null; require('mongoose').createConnection('mongodb://localhost:27018/foo', { replicaSet: 'RS', autoReconnect: true, readPreference: 'secondaryPreferred'}).then(r => conn=r).catch(r => console.log('---', r)); (async () => { for(let i = 0; i < 100000;i++) { try { let r = await conn.db.collection('tests').findOne({i: 1}); console.log(i, r); } catch(e) { console.log(e); } } })();
新統一トポロジー
let conn = null; require('mongoose').createConnection('mongodb://localhost:27018/foo', { replicaSet: 'RS', useUnifiedTopology: true, readPreference: 'secondaryPreferred'}).then(r => conn=r).catch(r => console.log('---', r)); (async () => { for(let i = 0; i < 100000;i++) { try { let r = await conn.db.collection('tests').findOne({i: 1}); console.log(i, r); } catch(e) { console.log(e); } } })();
注意
良い改善だとは思うが、ハイロードな環境下では、30秒ものオペレーションを貯めたり再送して重複処理するのは危険極まりない。
しっかり設計せずに移行するのは危険だ。