14.2 Redis Command Model, Transactions, and Persistence
Metadata Card
- Prerequisites: 14.1 Memory-Optimized Database
- Keywords: Redis, atomic commands, MULTI/EXEC, WATCH, RDB, AOF, replication
- Code Language: Redis CLI
Redis's strength lies in its "command model centered around data structures," not in simply moving relational tables into memory. Each data type (string, hash, list, set, sorted set, stream) offers atomic operations tailored to its use case. Choosing the wrong structure undermines memory efficiency, latency, and maintainability alike.
Prefer Atomic Commands over Read-Then-Overwrite
Use Redis 7+ with interactive redis-cli, keeping each sequence on one connection. Run the fixed keys in an empty practice instance. These examples demonstrate command semantics; they do not implement order deduplication or insufficient-stock checks.
INCR item:42:view_count
HINCRBY item:42 stock -1
ZINCRBY ranking 15 player:7HINCRBY uses the Hash item:42; the later item:42:stock is a separate String key. Do not mix these representations. Decrementing missing stock creates -1, so atomicity does not enforce non-negative inventory. These operations are atomic at the command boundary. If a client first runs GET, performs local computation, and then SET, multiple clients may overwrite each other's results.
From the client's perspective, Redis command execution is largely serial. However, modern Redis uses background threads, I/O threads, and child processes to handle other tasks. More accurately, "the core data structure operations for most commands are executed on the main execution thread," rather than "Redis has only one thread." Slow commands, very large keys, or Lua scripts can still block other requests.
MULTI/EXEC is not a relational database transaction
SET item:42:stock 1
SET order:created 0
MULTI
DECRBY item:42:stock 1
INCR order:created
EXECCommands queued after MULTI are executed consecutively at EXEC, ensuring no interleaving with commands from other clients. However, it lacks the automatic rollback behavior found in relational databases: if a command fails during execution due to type or other reasons, the remaining commands in the queue can still proceed.
Starting from the initialization above, EXEC returns 0 and 1 for remaining stock and created orders. Verify that a runtime error does not roll back other commands with this sequence:
SET lab:typed text
SET lab:completed 0
MULTI
INCR lab:typed
INCR lab:completed
EXEC
GET lab:completedINCR lab:typed reports a non-integer value, but the following INCR succeeds and GET lab:completed returns 1. A syntax error while queuing instead causes EXEC to reject the queue; that differs from a runtime error.
For conditional updates, use WATCH for optimistic checking:
SET item:42:stock 1
WATCH item:42:stock
GET item:42:stock
MULTI
DECRBY item:42:stock 1
EXECBetween GET and MULTI, the application must verify that stock is a valid integer greater than 0; otherwise UNWATCH and stop. This fixture starts at 1, so an uncontended EXEC returns 0. To force a conflict, run INCR item:42:stock in B after A's GET, then continue A from MULTI through EXEC. Expect a nil/null transaction result: the queue does not execute and stock remains 2. This differs from an empty array returned by an empty transaction. The client must re-read, re-evaluate, and retry with bounds. For multi-step validations, server-side Lua or Functions are better suited to consolidate the logic into a single atomic operation. Keep scripts short and bound their input sizes. Lua and Functions also lack runtime-error rollback, so complete predictable argument and type checks before modifying state.
RDB: Timepoint Snapshots
RDB periodically generates compact snapshots of the dataset. Typically, a child process writes the snapshot to a temporary file, then atomically replaces the old snapshot, while the parent process continues serving requests. Copy-on-write (COW) allows the parent and child to initially share memory pages.
Key trade-offs of RDB:
- Files are compact, leading to faster backups and quicker restarts for large datasets;
- Writes between snapshots may be lost in the event of a failure;
fork()itself can introduce latency, and during write-intensive periods, COW increases memory consumption;- RDB files still require copying to a separate fault domain; a single-machine file is not sufficient for disaster recovery.
It's not accurate to simply double peak memory usage, actual memory consumption depends on the proportion of memory pages modified during snapshotting, as well as allocator and kernel behavior. Capacity planning should be based on stress-tested peak loads.
AOF: Record Write Commands and Flush According to Strategy
AOF records commands that modify the dataset and replays them on restart. Common strategies for appendfsync:
| Strategy | Acknowledgment Path | Typical Data Window |
|---|---|---|
always | Requests fsync for each write batch before acknowledgment | Minimal, but still constrained by hardware and failure models |
everysec | Background fsync roughly once per second | Typically planned around a one-second window, not a hard bound during storage stalls |
no | Delegated to the operating system for flushing | Window determined by OS-level policies |
AOF grows over time and must be rewritten into a shorter, state-reconstructing form. Starting with Redis 7, multi-part AOF is adopted: a base file, one or more incremental files, and a manifest together describe the valid log set. The older model of simply writing "a single AOF file is replaced" is no longer sufficient.
Enable Both AOF and RDB at the Same Time
Both can be enabled simultaneously; during restart, Redis will prefer recovering from the more complete AOF file. An AOF base can use RDB format, which is not the same as "simultaneously saving independent RDB snapshots for backup."
Persistent configuration should be derived from business tolerance:
- A pure in-memory cache can disable persistence, but you must verify that the database can withstand a full cache loss during cache repopulation;
- When a seconds-scale data-loss window is acceptable, AOF
everyseccan be evaluated; - High-value authoritative data still requires replication, backups, recovery drills, and idempotent writes, relying solely on AOF is not sufficient;
- WAIT confirms that replicas processed preceding writes, which does not establish AOF persistence. Redis 7.2+ provides WAITAOF for local/replica AOF-flush waits. Check the acknowledgment counts actually returned. WAIT can wait for replica acknowledgments but won't automatically make Redis a strongly consistent CP database; failover may still be affected by persistence and replication configurations.
Eviction and Persistence Are Two Axes
The maxmemory-policy setting determines which keys are removed when memory usage reaches its limit; RDB or AOF determines how existing data is restored. An authoritative instance that permits eviction of business data, even with AOF fsync on every batch, may still persist the eviction operation faithfully.
Therefore, the instance's role must be clearly defined:
- Cache: Eviction is allowed; source data can be reconstructed;
- Session or queue: Requires explicit definition of semantics for data loss, duplication, and expiration;
- Authoritative state: Typically disallows arbitrary eviction and demands a reliability design more robust than single-node persistence.
Operations Monitoring
INFO persistence
INFO memory
SLOWLOG GET 20
LATENCY DOCTORMonitor the most recent RDB/AOF status, whether rewrite is stalled, fork/COW memory usage, memory fragmentation, slow commands, and master-slave lag. During recovery drills, actually start a new instance from backup and verify key counts, critical business objects, and application read/write operations; do not just check that files exist.
References
After completing this chapter, you should be able to decompose a "memory-only" system into four verifiable questions: data layout, execution model, persistence confirmation, and recovery budget.