Optimistic Locking
Optimistic locking with WATCH detects concurrent modifications before MULTI/EXEC commits — read version, queue updates, abort if another client changed the key.
Introduction
Optimistic locking with WATCH detects concurrent modifications before MULTI/EXEC commits — read version, queue updates, abort if another client changed the key. Inventory systems with low contention prefer retry over blocking locks.
EXEC returning empty multi-bulk means conflict — re-read and retry with backoff. High contention on same key makes optimistic locking thrash; switch to pessimistic SET NX or Lua.
Spring @Version on JPA is the ORM analog; WATCH is the Redis native pattern.
Understanding the topic
Key concepts
- WATCH key — track changes until EXEC.
- Read current state after WATCH.
- MULTI queue writes based on read.
- EXEC fails (nil) if watched key touched.
- Retry loop with exponential backoff.
- UNWATCH or EXEC/DISCARD clears watch.
sequenceDiagramClient->>Redis: WATCH keyClient->>Redis: MULTIClient->>Redis: commandsClient->>Redis: EXEC
Step-by-step explanation
- Client WATCHes keys involved in decision.
- Reads values (GET/HGET) outside MULTI.
- MULTI queues dependent writes.
- EXEC runs only if watched keys unchanged.
- On nil, client retries from WATCH.
Syntax reference
Common commands
- Minimize time between WATCH and EXEC.
- Watch all keys your logic depends on.
- Cap retries to avoid infinite loops.
WATCH stock:sku42qty = GET stock:sku42# if qty >= 1 locally:MULTIDECR stock:sku42EXEC# retry if EXEC returned (nil)
Informative example
Decrement stock only if sufficient — retry on conflict:
# Loop in application until EXEC succeeds or max retriesWATCH inventory:widgetGET inventory:widgetMULTIDECRBY inventory:widget 1EXEC
Production code wraps in while loop with max 3–5 retries. High-contention SKU → use Lua single script instead.
Real-world use
Real-world use cases
- Low-contention inventory decrement.
- Balance update with version check.
- Config update with read-verify-write.
- Counter adjust after read threshold.
- Cart merge conflict resolution.
Best practices
- Short watch window — few commands in MULTI.
- Max retry with jitter backoff.
- Switch to Lua on hot keys.
- Watch minimal key set.
- Log retry rate — high rate signals design issue.
- Don't WATCH across user think time.
Common mistakes
- Infinite retry on perpetual conflict.
- Watching wrong keys — stale commits.
- Using on flash-sale hot SKU — use Lua or queue.
- Assuming EXEC nil is error not conflict signal.
Advanced interview questions
Q1BeginnerOptimistic vs pessimistic lock?
Q2BeginnerEXEC returns nil meaning?
Q3IntermediateWhen prefer WATCH over SET NX lock?
Q4IntermediateFlash sale inventory pattern?
Q5AdvancedImplement optimistic lock in Spring + Redis?
Summary
WATCH + MULTI/EXEC = optimistic concurrency.