I'm working with a Redis cluster having 2+ nodes. I'm trying to figure out which tool best fits for handling concurrency - transaction or locking. Transactions are well documented, but I didn't find a good best-practice-example on redlock. I also wonder why two tools exist and what's the use case for each.
For simplicity, let's assume I want to do a concurrent increment and there is no INCR command in Redis.
Option 1. Using Transactions
If I understand correctly, NodeJS pseudocode would look like this:
transactIncrement = async (key) => {
await redisClient.watch(key);
let value = redisClient.get(key);
value = value + 1;
const multi = await redisClient.multi();
try {
await redisClient.set(key, value, multi);
await redisClient.exec(multi);
} catch (e) {
// most probably error thrown because transaction failed
// TODO: think if it's a good idea to restart in every case, introducing a potential infinite loop
// whatever, restart
await transactIncrement(key);
}
}
Bad things I can see above are:
- try-catch block
- possibility to use transactions with multiple keys is limited on redis cluster
Option 2. Redlock
Is it true that trying to lock a resource that's already locked would not cause a failure immediately? So that redlock tries N times before erroring?
If true then here's my pseudocode:
redlockIncrement = async (key) => {
await redlock.lock(key, 1);
// below this line it's guaranteed that other "threads" are put on hold
// and cannot access the key, right?
let value = await redisClient.get(key);
value = value + 1;
await redisClient.set(key, value);
await redlock.unlock(key);
}
Summary
If I got things right then redlock is definitely a more powerful technique. Please correct me if I'm wrong in the above assumptions. It would also be really great if someone provides an example of code solving similar problem because I couldn't find one.
Redlock is useful when you have a distributed set of components that you want to coordinate to create an atomic operation.
You wouldn't use it for operations that affect a single Redis node. That's because Redis already has much simpler and more reliable means of ensuring atomicity for commands that use its single-threaded server: transactions or scripting. (You didn't mention Lua scripting, but that's the most powerful way to create custom atomic commands).
Since
INCR
operates on a single key, and therefore on a single node, the best way to implement that would be with a simple Lua script.Now, if you want to use a sequence of commands that spans multiple nodes neither transactions nor scripting will work. In that case you could use Redlock or a similar distributed lock. However, you would generally try to avoid that in your Redis design. Specifically, you would use hash tags to force certain keys to reside on the same node: