Chapter 13 · Transactions, WATCH, Optimistic Locking, and Atomicity
MULTI/EXEC Queueing Semantics, Atomic Execution, and What Transactions Do Not Provide
See exactly when commands are queued, when they execute, what an EXEC reply proves, and why Redis transaction atomicity is not SQL rollback or a durability guarantee.
Learning outcomes
AtlasMart wants to mark an order as reserved and decrement a synthetic inventory counter without another client observing a half-applied Redis state. The first instinct is often “use a transaction,” but that phrase is dangerous unless you can say exactly what Redis queues, when execution starts, how errors appear, and which guarantees remain outside the transaction.
Explain MULTI as a connection state that queues later commands instead of executing them immediately.
Interpret QUEUED replies and the ordered EXEC reply array as separate phases of one transaction.
Prove that successful queued commands execute without another client interleaving inside the EXEC block.
Distinguish queue-time errors from runtime command errors and explain why Redis has no SQL-style rollback.
Separate transaction atomicity from persistence durability, replication/failover guarantees, and Cluster slot constraints.
All Chapter 13 mandatory labs reuse the disposable Chapter 01
environment: Redis Open Source 8.10.1 from Docker
Official Image redis:8.10.1, container
atlasmart-redis-ch01, standalone topology, host
publication 127.0.0.1:6379, TLS disabled only
because traffic stays on loopback, default ACL user disabled,
named users academy-admin and
atlasmart-app, logical database 0, AOF with
appendfsync everysec plus RDB snapshots,
persistent /data, and no explicit
maxmemory limit or eviction policy. Transaction
exercises use academy-admin because the Chapter
01 application ACL was intentionally not broadened with the
separate @transaction category; production
applications should grant only the commands and key patterns
they need. Fixtures stay under atlasmart:ch13:*.
1. The problem: two commands can expose an intermediate state
Without a transaction, two individually atomic commands are still two scheduling opportunities. If client A decrements inventory and then client B reads the order before client A updates its status, client B can observe a business state AtlasMart never intended to expose. Redis command atomicity means a single command is not interleaved internally; it does not automatically group separate commands.
| Layer | Guarantee | Not guaranteed |
|---|---|---|
| Single command | That command executes atomically on its target server | A multi-command business invariant |
| MULTI/EXEC | Queued commands execute sequentially as one uninterrupted server operation | Rollback after runtime errors |
| Persistence | Depends on RDB/AOF/fsync configuration | The same thing as transaction atomicity |
| Replication/failover | Separate asynchronous replication behavior | That a recent transaction survives every failover |
2. MULTI changes connection state; it does not execute the next command
MULTI returns OK and places that
client connection into transaction mode. Subsequent valid
commands are parsed and queued. Their immediate response is
QUEUED, not the command result. This is why
splitting a transaction across separate
redis-cli invocations is incorrect: transaction
state belongs to one connection.
docker exec -it -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin# Keep MULTI/WATCH and the commands they protect on this same redis-cli connection.
UNLINK atlasmart:ch13:l1:inventory atlasmart:ch13:l1:order:9001:statusSET atlasmart:ch13:l1:inventory 5SET atlasmart:ch13:l1:order:9001:status pendingMULTIDECR atlasmart:ch13:l1:inventorySET atlasmart:ch13:l1:order:9001:status reserved# Expected now: DECR -> QUEUED; SET -> QUEUED. Neither queued command result exists yet.EXEC# Expected EXEC array: 1) (integer) 4 2) OKMGET atlasmart:ch13:l1:inventory atlasmart:ch13:l1:order:9001:status# Expected final values: "4", "reserved"
3. EXEC is the execution boundary
EXEC runs the successfully queued commands in order
and returns an array whose element positions correspond to the
queued commands. Other clients are not served in the middle of
that execution. The result array is evidence about command
replies; it is not an application-level “commit record,” and it
does not say anything about an external payment API, file write,
or message broker.
| Observation | What it proves | What it does not prove |
|---|---|---|
| QUEUED | Command was accepted into this connection’s transaction queue | Command already changed data |
| EXEC array | Queued commands ran in order and each reply is visible | Every reply was successful |
| No interleaving during EXEC | Other clients did not run commands inside the transaction execution | No client could change data before MULTI or after EXEC |
| Final GET/MGET | Current Redis state matches the expected postcondition | Durable survival across crash/failover |
4. Runtime errors do not roll back successful siblings
A command can be syntactically valid when queued and still fail
when executed—for example, a List command against a String.
Redis places that error in the EXEC result array
and continues executing the other queued commands. This is the
key difference from SQL-style rollback.
UNLINK atlasmart:ch13:l1:runtime-count atlasmart:ch13:l1:runtime-statusSET atlasmart:ch13:l1:runtime-count 10MULTIINCR atlasmart:ch13:l1:runtime-countLPUSH atlasmart:ch13:l1:runtime-count impossibleSET atlasmart:ch13:l1:runtime-status finishedEXEC# Expected: integer 11, WRONGTYPE error, OK. Redis does not undo INCR or SET.MGET atlasmart:ch13:l1:runtime-count atlasmart:ch13:l1:runtime-status# Expected final values: "11", "finished"
If correctness requires “all effects succeed or none do,” do not obtain that property by assuming rollback. Validate types/invariants before queuing, prefer a safer single command where possible, or move tightly bounded logic into an atomic script/function when Chapter 14 reaches that tradeoff.
5. Queue-time errors are different: EXECABORT protects the queue
If Redis cannot queue a command—such as a wrong argument
count—the transaction is marked invalid. Modern Redis refuses
the later EXEC and discards the transaction. That
is a different failure class from a runtime
WRONGTYPE reply inside an otherwise executed
EXEC array.
UNLINK atlasmart:ch13:l1:q1 atlasmart:ch13:l1:q2MULTISET atlasmart:ch13:l1:q1 1INCR atlasmart:ch13:l1:q1 extra-argument# Expected immediately: ERR wrong number of arguments for incrSET atlasmart:ch13:l1:q2 2EXEC# Expected: EXECABORT Transaction discarded because of previous errors.MGET atlasmart:ch13:l1:q1 atlasmart:ch13:l1:q2# Expected: both nil because the queued transaction did not execute.
6. DISCARD abandons queued work before execution
DISCARD clears this connection’s transaction queue
and restores normal connection state. If WATCH was active,
DISCARD also unwatches the keys. It cannot travel backward in
time: once EXEC has executed commands, DISCARD is
not a rollback mechanism.
SET atlasmart:ch13:l1:discard-demo 1MULTIINCR atlasmart:ch13:l1:discard-demoSET atlasmart:ch13:l1:discard-marker queued-but-cancelledDISCARDGET atlasmart:ch13:l1:discard-demo# Expected: "1"GET atlasmart:ch13:l1:discard-marker# Expected: nil
7. Transaction atomicity is not durability
The lab uses append-only file (AOF) persistence with
appendfsync everysec plus RDB snapshots. That
configuration has its own crash/data-loss tradeoff. Redis
documents transaction handling in AOF separately from
client-visible atomic execution. A transaction that returned
successfully is not automatically equivalent to a database
commit synchronously forced to stable storage, and asynchronous
replication does not turn it into a universally durable write.
INFO persistenceCONFIG GET appendonlyCONFIG GET appendfsync# Record the live values next to any crash/durability claim; do not infer them from MULTI/EXEC.
8. Topology changes what a multi-key transaction can address
The mandatory lab is one standalone Redis node, so its keys
share one server. In Redis Open Source Cluster, keys
participating in a MULTI/EXEC transaction must be
in the same hash slot. Hash tags such as
{order:9001} can co-locate related keys, but they
can also create hot slots if overused. Cluster constraints are a
data-model decision, not a syntax detail.
SET atlasmart:ch13:{order:9001}:status pendingSET atlasmart:ch13:{order:9001}:inventory 5# In Cluster, both names hash the substring inside {...} to the same slot.# The standalone lab does not prove Cluster routing; Chapter 21 will test it on a real Cluster.
9. A pipeline is a transport optimization unless it is explicitly transactional
Pipelining reduces network round trips by buffering commands. It
is not inherently the same correctness primitive as
MULTI/EXEC. Some clients—redis-py included—make
their default pipeline() transactional, which can
hide the conceptual distinction. Always inspect the client
configuration instead of saying “pipeline means atomic.”
| Mechanism | Main purpose | Atomic group? |
|---|---|---|
| Plain sequential commands | Simple request/response | No |
| Non-transactional pipeline | Reduce round trips / improve throughput | No |
| MULTI/EXEC | Uninterrupted ordered execution | Yes, for the queued Redis commands |
| WATCH + MULTI/EXEC | Conditional optimistic transaction | Only if watched state is unchanged |
10. Security: transaction permission is separate from key permission
MULTI, EXEC, WATCH,
UNWATCH, and DISCARD are in Redis
transaction ACL categories. A user may have permission to
read/write a key yet lack permission to enter a transaction.
This chapter uses the disposable
academy-admin identity for mechanism labs rather
than silently broadening atlasmart-app. Production
should grant the minimum command set and key patterns needed.
ACL WHOAMIACL DRYRUN atlasmart-app MULTIACL DRYRUN atlasmart-app GET atlasmart:ch13:probe# Results depend on the exact Chapter 01 ACL file. Record them; do not assume @write implies @transaction.
11. Reproducible AtlasMart lab
Run the previous examples in one interactive
redis-cli process and keep every key inside the
Chapter 13 namespace. The lab intentionally includes one runtime
error and one queue-time error; both are bounded to synthetic
keys.
MGET atlasmart:ch13:l1:inventory atlasmart:ch13:l1:order:9001:statusMGET atlasmart:ch13:l1:runtime-count atlasmart:ch13:l1:runtime-statusEXISTS atlasmart:ch13:l1:q1 atlasmart:ch13:l1:q2GET atlasmart:ch13:l1:discard-demo# Verify the expected postconditions before cleanup.
12. Verification checklist
-
You observed
QUEUEDbefore EXEC and actual command replies only after EXEC. - The runtime WRONGTYPE error did not undo the successful INCR/SET commands around it.
- The queue-time syntax error caused EXECABORT and left both queued target keys absent.
- DISCARD left the pre-transaction value unchanged.
- You recorded persistence/ACL context instead of inferring it from transaction success.
13. Cleanup
UNLINK atlasmart:ch13:l1:inventory atlasmart:ch13:l1:order:9001:status atlasmart:ch13:l1:runtime-count atlasmart:ch13:l1:runtime-status atlasmart:ch13:l1:q1 atlasmart:ch13:l1:q2 atlasmart:ch13:l1:discard-demo atlasmart:ch13:l1:discard-marker atlasmart:ch13:{order:9001}:status atlasmart:ch13:{order:9001}:inventory
This deliberately names the synthetic fixtures. Do not replace
it with FLUSHDB or FLUSHALL on a
shared instance.
14. Production judgment
Use MULTI/EXEC when several Redis commands must run
without other clients interleaving and you do not need a
read-before-write condition. Keep the queue bounded because EXEC
latency includes the work of every queued command. Measure tail
latency under realistic command complexity and persistence
settings. In replication/Sentinel/Cluster designs, separately
evaluate failover loss windows, slot locality, retries, and
client topology refresh. Keep external side effects outside the
fiction of Redis atomicity: a successful EXEC cannot roll back
an HTTP payment or guarantee that a message broker accepted an
event.
Check your understanding
- When does a queued command actually change Redis state?
- Does a WRONGTYPE error inside EXEC roll back earlier successful commands?
- What does a queue-time syntax error do on modern Redis?
- Is appendfsync everysec a property of MULTI/EXEC?
- Why can Cluster change the data model for transactions?
Review the answers
When EXEC executes the transaction, not when the client receives QUEUED.
No. Runtime command errors are returned in the EXEC array and other queued commands still execute.
It marks the transaction invalid; EXEC refuses to execute the queued transaction.
No. It is a separate AOF durability configuration with its own failure window.
Redis Open Source Cluster transactions require involved keys to be in one hash slot.
15. Summary and next step
You can now read a Redis transaction trace correctly: MULTI enters queueing mode, valid commands return QUEUED, EXEC executes the accepted queue without client interleaving, and command replies—including errors—arrive in order. There is no SQL-style rollback. Lesson 2 adds WATCH so the transaction can depend on a value read before MULTI without silently acting on stale state.
Authoritative references
- Redis Open Source 8.10 release notes
- Redis 8.10 command reference
- Redis transactions
- MULTI
- EXEC
- DISCARD
- WATCH
- UNWATCH
- Redis multi-key operations
- Redis pipelining
- redis-py pipelines and transactions
- redis-py documentation
- SET
- DELEX
- MSETNX
- INCR
- HINCRBY
- EVAL
- FCALL
- Redis ACLs
- Redis persistence
- Redis replication
- Redis licenses