Module F-11·20 min read

What Redis transactions actually guarantee, why runtime errors do not roll back, WATCH-based optimistic locking for read-modify-write patterns, retry loops, and when WATCH contention demands Lua scripts instead.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

F-11 — Transactions: MULTI, EXEC, and Optimistic Locking with WATCH

Who this module is for: You need to execute multiple Redis commands as a unit — without another client's commands interleaving — but you are not sure how Redis transactions work, what "atomic" means in this context, or how to handle the read-modify-write pattern safely. This module covers MULTI/EXEC transactions, their limitations, and WATCH-based optimistic locking for conditional execution.


The Problem: Interleaved Commands

Suppose you want to transfer credits between two user accounts:

text

Between your read and write, another client could modify the same keys. The result: a race condition where credits appear or disappear. In PostgreSQL, you would wrap this in a transaction. In Redis, you use MULTI/EXEC.


MULTI/EXEC Transactions

text
text

Between MULTI and EXEC, every command returns QUEUED — it is added to the transaction queue but not executed. EXEC sends all queued commands to Redis, which executes them sequentially without any other client's commands interleaving.

What "atomic" means here: The commands execute in order, without interruption from other clients. It is not atomic in the database sense — there is no rollback on error.

DISCARD

text

Transaction Errors: Two Types

Syntax errors (caught at queue time): If a command is syntactically wrong, the error is returned at queue time. When you call EXEC, the entire transaction is discarded:

text

Runtime errors (caught at execution time): If a command is syntactically valid but fails at runtime (e.g., type mismatch), the error is returned for that specific command, but the rest of the transaction continues:

text

key3 was set successfully despite key2's error. Redis transactions do not roll back on runtime errors. This surprises most engineers coming from SQL databases. The reasoning: most runtime errors in Redis are programming bugs (wrong type), not transient failures. There is no mechanism to "undo" an already-executed SET.


The Critical Limitation: No Conditional Logic Inside Transactions

You cannot read a value inside MULTI/EXEC and use it to make a decision:

text

All reads inside a transaction return QUEUED — the actual values are only available after EXEC returns the full result array. By that time, you have already submitted the writes.

This means the naive MULTI/EXEC approach cannot implement "check balance then debit" — it can only implement "debit unconditionally as an atomic unit."

For conditional operations, you need WATCH.


WATCH: Optimistic Locking

WATCH implements optimistic concurrency control. It monitors one or more keys and makes the subsequent transaction conditional: if any watched key is modified by any client between WATCH and EXEC, the transaction is aborted (returns nil instead of executing).

text

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.