Last active
August 28, 2022 15:34
-
-
Save kevcodez/aca13cdad183777f9b86d70f5076dcae to your computer and use it in GitHub Desktop.
Advanced Distributed Lock with Mongo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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