Distributed locks and why naive SETNX is wrong
A Redis lock is a lease that can expire while you still believe you hold it.
Open this lesson in the learning hubKey points
- The correct acquire is one command:
SET lock:job token NX PX 30000, with a token unique to this holder. SETNXfollowed byEXPIREis two commands, and a crash in between leaves a lock that never expires.- Releasing with plain
DELis a bug: if your lease already expired you delete a lock somebody else now owns. - The safe release is a Lua compare-and-delete - delete the key only if its value still equals your token.
- Every lock is a lease, so a GC pause or a slow disk longer than the TTL means two clients believe they hold it.
- A fencing token is a number that only ever increases, checked by the resource so a stale holder gets rejected.
Example
# acquire: one command, unique token, always a TTL
SET lock:invoice:991 4f2a-9c NX PX 30000
# release: compare and delete, never a bare DEL
EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
end
return 0" 1 lock:invoice:991 4f2a-9c
# fencing token: a number the resource can reject if it goes backwards
INCR fence:invoice:991 # -> 34, send it with every write
SET NX PX to acquire, Lua compare-and-delete to release, and a fencing token if correctness truly matters.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Redis Course course, and every lesson in it is listed on the Redis Course contents page.