これを投稿するのに最適な場所ではないかもしれませんが、とにかく投稿する価値があると思います。
model.syncIndexes()
データベース接続に対してモデルが定義/作成されるたびに呼び出します。これにより、インデックスがスキーマで最新かつ最新であることが保証されますが、オンラインで強調表示されているため(例)、分散アーキテクチャで問題が発生する可能性があります。複数のサーバーが同時に同じ操作を試みている場合。これは、ライブラリのようなものを使用して、同じマシン上の複数のコアでマスター/スレーブインスタンスを生成する場合に特に関係があります。これはcluster
、サーバー全体の起動時に、それらが互いに近接して起動することが多いためです。
上記の「codebarbarian」の記事を参照すると、次のように述べられている場合、問題が明確に強調されています。
MongooseはsyncIndexes()を呼び出さないので、syncIndexes()を自分で呼び出す必要があります。これにはいくつかの理由があります。特に、syncIndexes()が分散ロックを実行しないことが理由です。起動時にsyncIndexes()を呼び出すサーバーが複数ある場合は、存在しなくなったインデックスを削除しようとするとエラーが発生する可能性があります。
だから私がやっていることは、複数のワーカー(そして実際には複数のサーバーの複数のワーカー)が同時に同じ同期操作を試みるのを防ぐために、redisとredisredlockを使用してわずかな期間リースを取得する関数を作成することです。
また、操作を実行しようとしているのが「マスター」でない限り、すべてをバイパスします。このジョブをどのワーカーにも委任することに意味はありません。
const cluster = require('cluster');
const {logger} = require("$/src/logger");
const {
redlock,
LockError
} = require("$/src/services/redis");
const mongoose = require('mongoose');
// Check is mongoose model,
// ref: https://stackoverflow.com/a/56815793/1834057
const isMongoModel = (obj) => {
return obj.hasOwnProperty('schema') && obj.schema instanceof mongoose.Schema;
}
const syncIndexesWithRedlock = (model,duration=60000) => new Promise(resolve => {
// Ensure the cluster is master
if(!cluster.isMaster)
return resolve(false)
// Now attempt to gain redlock and sync indexes
try {
// Typecheck
if(!model || !isMongoModel(model))
throw new Error('model argument is required and must be a mongoose model');
if(isNaN(duration) || duration <= 0)
throw new Error('duration argument is required, and must be positive numeric')
// Extract name
let name = model.collection.collectionName;
// Define the redlock resource
let resource = `syncIndexes/${name}`;
// Coerce Duration to Integer
// Not sure if this is strictly required, but wtf.
// Will ensure the duration is at least 1ms, given that duration <= 0 throws error above
let redlockLeaseDuration = Math.ceil(duration);
// Attempt to gain lock and sync indexes
redlock.lock(resource,redlockLeaseDuration)
.then(() => {
// Sync Indexes
model.syncIndexes();
// Success
resolve(true);
})
.catch(err => {
// Report Lock Error
if(err instanceof LockError){
logger.error(`Redlock LockError -- ${err.message}`);
// Report Other Errors
}else{
logger.error(err.message);
}
// Fail, Either LockError error or some other error
return resolve(false);
})
// General Fail for whatever reason
}catch(err){
logger.error(err.message);
return resolve(false);
}
});
他のスレッドの主題であるRedis接続のセットアップについては説明しませんが、上記のコードのポイントは、あるsyncIndexes()
スレッドがインデックスをドロップし、別のスレッドが同じインデックスをドロップしようとする問題を確実に使用して防止する方法を示すことです。 、またはインデックスを同時に変更しようとするその他の分散した問題。