Reading — step 1 of 3
Optimistic Locking with WATCH
WATCH — Optimistic Locking for Transactions
MULTI/EXEC groups commands so they run atomically. But what if you need to read a value, then write based on it — and you want the write to abort if someone else changed the value in between?
This is the classic check-then-act race. Two clients try to "buy" the last item:
Client A: GET stock → 1
Client B: GET stock → 1
Client A: SET stock 0 → OK (sold!)
Client B: SET stock 0 → OK (...also sold the same item?)
Pessimistic locks solve this but kill throughput. Redis offers a better answer: WATCH — optimistic concurrency control.
How WATCH Works
WATCH stock → snapshot stock's version
GET stock → 1
MULTI → enter transaction
SET stock 0 → +QUEUED
EXEC → check: has stock's version changed since WATCH?
no → run the queued commands, return results
yes → abort, return $-1\r\n (nil)
If EXEC aborts, the client typically loops back and retries. This is the same pattern as compare-and-swap in lock-free programming, or MVCC in databases.
Implementation: Version Counters
Track a versions map: key → integer. Every write command increments versions[key]. WATCH snapshots the version for each watched key. EXEC compares snapshots to current — any mismatch means a watched key was touched, and the transaction is dirty.
UNWATCH
UNWATCH clears the watched dict — useful if you've decided not to proceed with a transaction. EXEC and DISCARD also implicitly UNWATCH.
Why "Optimistic"?
Optimistic = "assume the conflict won't happen, check at the end". Cheap when conflicts are rare. The opposite is pessimistic locking — hold a mutex the whole time — which kills throughput when conflicts are rare (which they usually are).
Redis WATCH is what lets you build atomic counters, distributed queues, and CAS-style updates without slowing down the common case.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…