Skip to main content
Cache-Pot transactions let you queue a series of commands and execute them atomically with MULTI/EXEC. Between MULTI and EXEC, every command you send is acknowledged with QUEUED rather than being run immediately. When you call EXEC, all queued commands run in order and their results are returned as an array. Use WATCH for optimistic locking — if any watched key is modified by another client before you call EXEC, the transaction is automatically aborted.
Unlike single-threaded Redis, Cache-Pot does not hold a global lock during EXEC. Each queued command is individually atomic, and WATCH correctly detects concurrent changes to watched keys, but commands from other connections can interleave between your queued commands. This best-effort isolation is sufficient for the common optimistic lock pattern.

MULTI

Begins a transaction block. All subsequent commands are queued rather than executed, and each is acknowledged with +QUEUED. Nested MULTI calls are not allowed — Cache-Pot returns an error if you issue MULTI while already inside a transaction. Syntax: MULTI
MULTI
# OK

SET counter 1
# QUEUED

INCR counter
# QUEUED
Returns: OK.

EXEC

Executes all commands queued since MULTI and returns their results as an array. Exits the transaction block automatically. If WATCH was active and any watched key changed since it was watched, EXEC aborts and returns a null array — nothing is executed. If a command was queued that could not be recognised (unknown command, or a forbidden command like SUBSCRIBE or MONITOR), the transaction is marked dirty and EXEC returns an EXECABORT error without running anything. Syntax: EXEC
MULTI
INCR counter
SET last_updated "2024-01-01"
EXEC
# 1) (integer) 1
# 2) OK
Returns: Array of replies from each queued command, in order. Returns a null array if a watched key was modified. Returns an EXECABORT error if the transaction was dirtied by an invalid queued command.

DISCARD

Aborts the current transaction, clears all queued commands, releases all WATCHes, and exits the transaction block. Has no effect on data. Syntax: DISCARD
MULTI
SET foo "bar"
DISCARD
# OK

GET foo
# (nil)  (SET was never executed)
Returns: OK, or an error if called outside a MULTI block.

WATCH

Marks one or more keys for optimistic locking. If any watched key is written to by any client between the WATCH call and EXEC, the transaction is aborted and EXEC returns a null array. WATCH must be called before MULTI. Syntax: WATCH key [key ...]
WATCH balance
# OK

# Inspect current value before deciding what to do
GET balance
# "100"

MULTI
DECRBY balance 10
EXEC
# If balance was NOT changed by another client:
# 1) (integer) 90

# If balance WAS changed by another client between WATCH and EXEC:
# (nil)  <- transaction aborted, retry the whole sequence
Returns: OK. Returns an error if called inside an active MULTI block.

UNWATCH

Releases all keys currently being watched by this connection, without aborting a transaction. Calling EXEC or DISCARD also releases watches automatically. Syntax: UNWATCH
WATCH key1 key2
# OK

# Decide you don't need the lock after all
UNWATCH
# OK
Returns: OK.

Example: atomic counter update

The simplest transaction: increment a counter and record the timestamp in a single atomic operation.
MULTI
INCR page_views
SET page_views_updated "2024-01-01T12:00:00Z"
EXEC
# 1) (integer) 1
# 2) OK

Example: optimistic locking with retry

Use WATCH to implement a compare-and-swap. If the transaction aborts, re-read the value and retry:
# Step 1: watch the key
WATCH balance
# OK

# Step 2: read the current value
GET balance
# "100"

# Step 3: open a transaction and make your changes
MULTI
DECRBY balance 10
SET balance_log "deducted 10 at 2024-01-01"
EXEC
# If no concurrent modification:
# 1) (integer) 90
# 2) OK

# If another client changed balance between WATCH and EXEC:
# (nil)  <- abort; repeat from Step 1
Wrap your WATCH + MULTI + EXEC sequence in a retry loop in your application. A null reply from EXEC is not an error — it is a normal signal to re-read the latest state and try again.
The commands SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, and MONITOR cannot be queued inside a MULTI block. Attempting to queue any of them marks the transaction as dirty — the subsequent EXEC will return EXECABORT and run nothing.