Skip to content

Instantly share code, notes, and snippets.

@kevcodez
Last active August 28, 2022 15:34
Show Gist options
  • Save kevcodez/aca13cdad183777f9b86d70f5076dcae to your computer and use it in GitHub Desktop.
Save kevcodez/aca13cdad183777f9b86d70f5076dcae to your computer and use it in GitHub Desktop.
Advanced Distributed Lock with Mongo
/**
* Create the MongoDB collection and an expiring index on a field named "expiresAt".
*
* db.createCollection('locks');
* db.locks.createIndex( { "expiresAt": 1 }, { expireAfterSeconds: 0 } )
**/
async function acquireLock(name, ttlSeconds) {
const collection = await db.collection('locks');
// Entry gets removed automatically due to an expiry index in Mongo
const expiresAt = addSeconds(new Date(), ttlSeconds);
try {
await collection.insertOne({
_id: name,
expiresAt,
});
return true;
} catch (ex) {
// 11000 means duplicate key error, which is expected on an active lock
if (ex.code !== 11000) {
// Unexpected error, what happened here :o
throw ex
}
// As we got a duplicate key exception, no lock could be acquired
return false;
}
}
async function releaseLock(name) {
const collection = await getCollection();
await collection.deleteOne({ _id: name });
}
async function acquireAndExecute({ name, ttlSeconds }, fn) {
const locked = await acquireLock(name, ttlSeconds);
if (!locked) {
// Lock could not be acquired
return;
}
try {
await fn();
// Release the lock when your function is done
await releaseLock(name);
} catch (ex) {
// Release lock in case of exception
await releaseLock(name);
}
}
/**
* The lock service helps acquire locks in a distributed environment. Imagine a CRON job running at 18:00, but you have 4 server instances running.
* If you only want the job to run once and not 4 times in parallel, you need to determine the leading instance.
*
* To do so, we acquire a lock through MongoDB that gets released upon job completion or after the entry expires (by using a TTL).
* The first caller will be able to acquire the lock, all other callers will run into a duplicate key error and cannot acquire the lock.
*/
export const lockService = {
acquireLock,
releaseLock,
acquireAndExecute,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment