Skip to main content

Transactions

MULTI, EXEC, DISCARD, WATCH

A transaction in Redis is a sequence of commands that are executed in order, as a single isolated operation.

  • It ensures atomicity → either all commands execute or none do.
  • Transactions in Redis use the commands:
    • MULTI → Start a transaction
    • EXEC → Execute the transaction
    • DISCARD → Cancel the transaction
    • WATCH → Watch keys for changes (optimistic locking)

MULTI

  • Marks the start of a transaction.
  • After MULTI, commands are queued, not executed immediately.
  • The queued commands will run only when EXEC is called.
MULTI
SET user:1 "Masum"
INCR counter
EXEC
  • MULTI → starts transaction
  • SET and INCR are queued
  • EXEC → executes both in sequence
  • Atomicity ensures that either both run successfully or none run

EXEC

  • Executes all commands queued after MULTI.
  • If a command fails (like syntax error), only that command fails, but others run.
  • The whole transaction is atomic from an isolation perspective → no other client’s command can interleave between them.

DISCARD

  • Cancels a transaction.
  • Clears all commands queued after MULTI.

WATCH

  • Provides optimistic locking.
  • You can WATCH one or more keys.
  • If any watched key is modified by another client before EXEC, the transaction is aborted (returns nil).

Useful for implementing check-and-set (CAS) logic.

Preventing Race Conditions with WATCH

Client A:

WATCH balance
val = GET balance # suppose balance = 100
MULTI
SET balance 90 # deduct 10
EXEC

If Client B modifies balance before Client A executes EXEC:

SET balance 50

Then Client A’s EXEC will fail:

(nil)
  • WATCH balance → monitors the balance key.
  • Since another client modified it before EXEC, Redis aborts the transaction.
  • This prevents race conditions (like double spending).

Transaction Flow Summary

  • WATCH key1 key2 → (optional) watch keys for changes.
  • MULTI → start transaction.
  • Queue commands (SET, INCR, etc.).
  • EXEC → execute transaction if watched keys unchanged.
    • If watched keys were modified → transaction aborts.
  • DISCARD → cancel transaction manually if needed.

Optimistic Locking

  • Optimistic locking is a technique to handle concurrent updates safely without using traditional locks.
  • In Redis, it is implemented via the WATCH command.
  • The idea: “I hope no one else changes this key while I’m working on it.”
  • If the key changes before the transaction executes, Redis aborts the transaction to prevent race conditions.

This is particularly useful in high-concurrency environments like counters, wallets, or stock management.

How Optimistic Locking Works

  1. WATCH keys → Redis monitors these keys.

  2. Read the key(s) → get their current values.

  3. MULTI → start a transaction and queue commands.

  4. EXEC → attempt to execute transaction.

    • If any watched key changed since WATCH, transaction fails (returns nil).
    • Otherwise, all queued commands execute atomically.

Key point: Optimistic locking is non-blocking. Other clients can still read/write the key; your transaction simply aborts if conflict occurs.

Bank Account Transfer

Imagine we want to deduct $10 from Alice’s balance safely, even if multiple clients try to modify it.

  1. Setup: SET balance:alice 100
  2. Safe Deduction Using WATCH
WATCH balance:alice         # Monitor the key

val = GET balance:alice # Suppose val = 100
if val >= 10:
MULTI # Start transaction
DECRBY balance:alice 10 # Deduct 10
EXEC # Attempt to execute transaction

Scenario 1: No other client modifies balance

  • EXEC succeeds → balance becomes 90.

Scenario 2: Another client modifies balance before EXEC

SET balance:alice 50
  • EXEC fails → returns (nil)
  • You can retry the transaction if needed.

Ensuring atomic operations

  • Atomic operations are operations that are completed entirely or not at all, with no interference from other clients.
  • In Redis, atomicity is built into many commands by default.
  • For example, commands like INCR, DECR, SETNX, HSET, and list operations like LPUSH are atomic.
  • This means you don’t need a transaction for single operations, even in a concurrent environment.

How Redis Ensures Atomicity

  1. Single-threaded execution:

    • Redis runs commands one at a time on a single main thread. This ensures no two commands from different clients can interleave.
  2. MULTI/EXEC transactions:

    • For multi-command operations, you can use MULTIEXEC.
    • Redis executes all queued commands atomically after EXEC.
  3. Optimistic locking (WATCH):

    • Ensures atomicity when you need to check a key’s value before modifying it.

Examples of Atomic Operations

  1. Atomic INCR

    SET counter 0
    INCR counter

    Even if multiple clients run INCR counter simultaneously, Redis ensures each increment is applied exactly once.

  2. Atomic Check-and-Set (SETNX)

    SETNX lock "1"
    • SETNX = "SET if Not eXists"
    • If the key doesn’t exist → sets it and returns 1
    • If the key exists → does nothing and returns 0
    • Useful for implementing distributed locks safely.
  3. Atomic Multi-Key Operation with Transaction

    Suppose you want to transfer money from Alice to Bob:

    WATCH balance:alice balance:bob   # Watch keys for changes
    alice_balance = GET balance:alice

    if alice_balance >= 10:
    MULTI
    DECRBY balance:alice 10
    INCRBY balance:bob 10
    EXEC
    else:
    DISCARD
  • MULTI → queue multiple commands
  • EXEC → executes all commands atomically
  • WATCH → ensures no one else modifies balances during this transaction
  1. Atomic List Operation

    LPUSH queue "task1"

    Even if multiple clients push to the same list simultaneously, Redis ensures each push is atomic.

Tips to Ensure Atomicity in Redis

  1. Use single commands whenever possible → they are already atomic.
  2. For multiple commands on related keys, use MULTI/EXEC to group them.
  3. Use WATCH if you need to implement conditional updates safely.
  4. Avoid long-running scripts or operations inside transactions → can block other clients.