> ## Documentation Index
> Fetch the complete documentation index at: https://cache-pot.thatdevguy.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Transactions in Cache-Pot: MULTI, EXEC, WATCH

> Cache-Pot supports atomic transactions with MULTI/EXEC. Use WATCH for optimistic locking to prevent concurrent modification.

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.

<Note>
  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.
</Note>

***

### 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`

```redis theme={null}
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`

```redis theme={null}
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 `WATCH`es, and exits the transaction block. Has no effect on data.

**Syntax:** `DISCARD`

```redis theme={null}
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 ...]`

```redis theme={null}
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`

```redis theme={null}
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.

```redis theme={null}
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:

```redis theme={null}
# 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
```

<Tip>
  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.
</Tip>

<Warning>
  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.
</Warning>
