Learnitweb

Redis Single-Threaded Execution and Transactions with Multi-Terminal Simulation

Redis is single-threaded for command execution, which provides atomicity at the command level. However, when multiple commands are executed together, or when multiple clients perform operations simultaneously, race conditions may occur. Redis provides transactions and the WATCH command to handle these cases safely.


1. Single-Threaded Execution in Redis

  • Redis executes commands sequentially in a single main thread.
  • No need for locks or synchronization for individual commands.
  • Example: Transferring money between users without transactions can produce incorrect results due to concurrent operations.

Example Without Transactions

Two keys in Redis:

user:1:balance = 1
user:2:balance = 0

Objective: Transfer 1 unit from user:1 to user:2.

If two clients execute commands concurrently:

  • Client A reads user:1:balance = 1
  • Client B reads user:1:balance = 1
  • Client A decrements → user:1:balance = 0
  • Client B decrements → user:1:balance = -1
  • Client A increments → user:2:balance = 1
  • Client B increments → user:2:balance = 2

Result: user:1:balance = -1, user:2:balance = 2 → incorrect.


2. Simulating Multiple Clients in Redis

To simulate concurrency:

  1. Open two terminals and connect both to Redis.
  2. Flush the database in both terminals:
FLUSHDB
  1. Initialize balances:
SET user:1:balance 1
SET user:2:balance 0
  1. Both terminals attempt to transfer money at the same time using DECR and INCR commands.
    Without transactions, you can observe the race condition, leading to negative balances or inconsistent results.

3. Using Transactions with MULTI and EXEC

Redis transactions allow you to execute multiple commands atomically.

  1. Start transaction mode:
MULTI
  1. Queue commands:
DECR user:1:balance
INCR user:2:balance
  1. Execute all commands as a single block:
EXEC
  • All queued commands are executed together.
  • Results are returned for each command.
  • If all commands succeed, changes are committed.
  • If a command fails, the entire transaction is rolled back.

Example With Two Terminals

Terminal 1:

MULTI
DECR user:1:balance
INCR user:2:balance

Terminal 2:
Simultaneously issues the same transaction.

When EXEC is called:

  • Terminal 1 executes successfully.
  • Terminal 2 may fail if the relevant keys were modified by Terminal 1.
  • Redis ensures atomicity and consistency.

4. Using WATCH to Prevent Conflicts

  • WATCH monitors keys for changes before executing a transaction.
  • If a watched key is modified by another client, the transaction fails automatically.

Example:

WATCH user:1:balance user:2:balance
MULTI
DECR user:1:balance
INCR user:2:balance
EXEC
  • If another client modifies user:1:balance during this transaction, EXEC returns nil, preventing inconsistent updates.
  • After EXEC, the watch is automatically removed.
  • If you want to abort a transaction manually, use:
DISCARD

This cancels all queued commands in the current transaction.


5. Key Points

  1. Redis executes commands sequentially in a single thread, making individual operations atomic.
  2. Multiple related commands require transactions (MULTI + EXEC) to ensure correctness.
  3. Use WATCH when multiple clients may modify the same keys simultaneously.
  4. Transactions ensure all-or-nothing execution, crucial for operations like:
    • Money transfers
    • Inventory updates
    • Booking systems
  5. DISCARD allows rolling back queued commands before execution if needed.

6. Summary

Using transactions and WATCH in Redis:

  • Prevents race conditions.
  • Ensures atomic execution of multiple commands.
  • Automatically handles conflicts between concurrent clients.
  • Removes the need for complex locking mechanisms in your application.