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秒ものオペレーションを貯めたり再送して重複処理するのは危険極まりない。
しっかり設計せずに移行するのは危険だ。