Postgresql provides a variety of ways to lock a row, but if you are looking to increase throughput, holding onto a valuable connection (even with a pooler) just for a lock isn’t ideal.
ZooKeeper is a popular distributed locking solution, and is relatively straightforward to run (particularly if you don’t mind risking a single node).
If you want a traditional lock, with a queue, you need a sequence node; but if you’re happy to bail out when the lock is already taken (equiv to SELECT ... FOR UPDATE NOWAIT
) then you only need a single ephemeral lock node.
This client lib mimics the java API, but it’s simple to add a wrapper making it more idiomatic:
const {promisify} = require('util'); const zookeeper = require('node-zookeeper-client'); module.exports = function({ uri }) { this.connect = function() { return new Promise((resolve) => { var client = zookeeper.createClient(uri); client.once('connected', function () { resolve({ create: function(path) { return promisify(client.create).bind(client)(path, null, zookeeper.ACL.OPEN_ACL_UNSAFE, zookeeper.CreateMode.EPHEMERAL); }, close: client.close.bind(client), }); }); client.connect(); }); }; };
And use this to hold a lock, while awaiting a promise:
module.exports = function(zookeeper, errorCodes) { return async function withLock(id, cb) { const client = await zookeeper.connect(); try { const nodeName = `/foo_${id}`; await client.create(nodeName); const res = await cb(); return res; } catch (err) { if (err.name === "NODE_EXISTS") { throw ...; } throw err; } finally { client.close(); } }; };