Script Valley
Redis: Complete Course
Transactions, Scripting, and PipelinesLesson 5.2

WATCH and optimistic locking in Redis: how to handle concurrent updates

WATCH command, optimistic locking, CAS pattern, MULTI after WATCH, EXEC returning nil on conflict, retry loop, WATCH vs pessimistic locking

Optimistic locking with WATCH

WATCH implements optimistic locking. It monitors one or more keys. If any watched key is modified by another client before your EXEC, the transaction is aborted — EXEC returns nil instead of the results array.

Pattern: safe balance transfer

async function transfer(from, to, amount) {
  while (true) {
    await client.watch(`balance:${from}`);
    const balance = parseInt(await client.get(`balance:${from}`));

    if (balance < amount) {
      await client.unwatch();
      throw new Error('Insufficient funds');
    }

    const result = await client
      .multi()
      .decrBy(`balance:${from}`, amount)
      .incrBy(`balance:${to}`, amount)
      .exec();

    if (result !== null) return result; // success
    // null means conflict — retry
  }
}

When WATCH is appropriate

Use WATCH when you need to read a value, make a decision based on it, and write — and the decision must be based on fresh data. Under high contention the retry loop can spin, so WATCH is best for low-to-medium contention. For high contention, a Lua script (lesson 5.4) is faster because it runs entirely server-side without round trips.

Up next

Redis pipelines: batch commands to reduce network round trips

Sign in to track progress