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.

Advanced170–220 minutesTransactions and concurrencyRedis Open Source 8.10.1Free/local-firstLast reviewed: September 6, 2026

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.

01

Explain MULTI as a connection state that queues later commands instead of executing them immediately.

02

Interpret QUEUED replies and the ordered EXEC reply array as separate phases of one transaction.

03

Prove that successful queued commands execute without another client interleaving inside the EXEC block.

04

Distinguish queue-time errors from runtime command errors and explain why Redis has no SQL-style rollback.

05

Separate transaction atomicity from persistence durability, replication/failover guarantees, and Cluster slot constraints.

Exact lab baseline

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.

Terminal · open one persistent redis-cli 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.
redis-cli · queue two writes on the same 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.

redis-cli · deliberately inject a runtime WRONGTYPE error
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"
Failure lesson

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.

redis-cli · queue-time syntax failure
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.

redis-cli · discard queued work
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.

redis-cli · capture the persistence context used by the lab
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.

redis-cli · illustrate the future Cluster naming pattern without requiring Cluster
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.

redis-cli · inspect ACL behavior without changing production policy
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.

redis-cli · final state verification
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 QUEUED before 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

redis-cli · bounded Chapter 13 Lesson 1 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

  1. When does a queued command actually change Redis state?
  2. Does a WRONGTYPE error inside EXEC roll back earlier successful commands?
  3. What does a queue-time syntax error do on modern Redis?
  4. Is appendfsync everysec a property of MULTI/EXEC?
  5. 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

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.