Redis Cheatsheet
Persistence and Config
Use this Redis reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
RDB snapshots
An RDB file is a compact point-in-time dump of the whole dataset. Redis forks, the child writes the file, and the parent keeps serving.
| Command | What it does |
|---|---|
BGSAVE [SCHEDULE] | Fork and snapshot in the background |
SAVE | Snapshot on the main thread, blocking every client. Never in production |
LASTSAVE | Unix time of the last successful save |
BGREWRITEAOF | Rewrite the AOF, compacting it |
DEBUG RELOAD | Save and reload from disk, a diagnostic, not a backup |
# redis.conf save 3600 1 # after 3600s if at least 1 key changed save 300 100 # after 300s if at least 100 keys changed save 60 10000 # after 60s if at least 10000 keys changed save "" # DISABLE snapshotting entirely dir /var/lib/redis # the working directory dbfilename dump.rdb # the RDB filename inside dir rdbcompression yes # LZF-compress strings rdbchecksum yes # CRC64 footer, ~10 percent slower loads stop-writes-on-bgsave-error yes # fail loudly when saves break
redis-cli CONFIG GET save # "3600 1 300 100 60 10000" redis-cli BGSAVE redis-cli LASTSAVE # compare before and after to confirm redis-cli INFO persistence | grep -E 'rdb_(bgsave_in_progress|last_bgsave_status)'
The gotchas are all about the fork:
- The fork can double memory. Copy-on-write means the child shares pages until the
parent writes them, so a write-heavy instance can approach 2× RSS during a save. Provision
headroom and set vm.overcommit_memory = 1 on Linux or the fork itself can fail.
- Losing everything since the last snapshot is the design. save 60 10000 means up to a
minute of writes are gone after a hard kill.
- stop-writes-on-bgsave-error yes makes Redis refuse writes once a background save
fails. That is intentional, and it is the failure people misread as a Redis bug when the
disk fills up.
AOF
The append-only file logs every write command, so recovery replays them. It is the durable option.
appendonly yes appendfsync everysec # always | everysec | no appenddirname "appendonlydir" # 7.0+, a DIRECTORY not a file appendfilename "appendonly.aof" # base name inside that directory no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 # rewrite when it doubles auto-aof-rewrite-min-size 64mb aof-use-rdb-preamble yes # the base file is RDB format, loads faster aof-timestamp-enabled no # 7.0+, annotate for point-in-time recovery
appendfsync | Durability | Cost |
|---|---|---|
always | Every write is fsynced before the reply | Slowest, bounded by disk latency |
everysec | At most 1 second of writes lost (default) | Near-zero impact, the right default |
no | Whenever the OS decides, typically 30s | Fastest, weakest |
AOF is a directory since 7.0, not a single file. The multi-part layout is
appendonlydir/ containing a base file (RDB-encoded), one or more incremental files, and a
.manifest that lists them.
appendonlydir/ appendonly.aof.1.base.rdb # snapshot at the last rewrite appendonly.aof.1.incr.aof # commands since then appendonly.aof.manifest # which files, in what order
This replaced the old in-place rewrite, which had to buffer new writes in memory during a
rewrite and could spike badly. A 7.0 rewrite just writes a fresh base and starts a new incr
file, so BGREWRITEAOF is far cheaper than it used to be.
redis-cli CONFIG SET appendonly yes # enables it live, triggers a rewrite redis-cli BGREWRITEAOF redis-cli INFO persistence | grep -E 'aof_(enabled|rewrite_in_progress|last_write_status)' redis-check-aof --fix appendonlydir/appendonly.aof.manifest # truncated tail repair redis-check-rdb dump.rdb
Choosing, and the honest trade
| RDB only | AOF only | Both (recommended) | |
|---|---|---|---|
| Worst-case data loss | Since the last snapshot, minutes | 1s with everysec, 0 with always | 1s |
| Restart time on a large dataset | Fast, it is a binary load | Slower, it replays commands | Fast, the base is RDB |
| File size | Smallest | Larger, compacted by rewrites | Both files on disk |
| Good for backups | Yes, one portable file | Awkward, it is a directory | Use the RDB half |
| Write throughput cost | Fork pauses only | Continuous appends plus fsync | Both |
| Corruption blast radius | The whole snapshot | A truncated tail is repairable | Best case |
The default posture for a durable store is both on, with appendfsync everysec. Redis
prefers the AOF on startup when it exists, so the RDB is your portable backup artifact and
the AOF is your recovery mechanism.
For a pure cache, turn both off (save "", appendonly no). Persistence on a cache buys
nothing and costs fork pauses on every save. That is what a throwaway instance is started
with:
redis-server --port 6399 --save '' --appendonly no
And the blunt caveat: even appendfsync always gives you single-node durability, not
transactional guarantees. Replication is asynchronous by default, so an acknowledged write
can be lost when a primary fails over. Redis is not a database of record.
Memory limits
CONFIG SET maxmemory 2gb CONFIG SET maxmemory-policy allkeys-lru # default is noeviction CONFIG GET maxmemory-samples # 5, raise to ~10 for tighter LRU CONFIG SET lazyfree-lazy-eviction yes # free evicted keys off the main thread
| Setting | Note |
|---|---|
maxmemory | 0 means unlimited, which means the OOM killer decides |
maxmemory-policy | Default noeviction, so writes fail at the limit |
maxmemory-samples | LRU and LFU are approximate, this is the sample size |
maxmemory-clients | 7.0+, caps total client buffer memory |
lazyfree-lazy-* | eviction, expire, server-del, user-del, all worth yes |
maxmemory counts the dataset plus buffers but not the copy-on-write overhead of a
fork, so set it to roughly half of physical memory on a persisted instance. The eviction
policy table is in the keys and expiry topic.
CONFIG
| Command | What it does |
|---|---|
CONFIG GET pattern [pattern ...] | Read parameters, globs allowed |
CONFIG SET p v [p v ...] | Change at runtime, multiple pairs since 7.0 |
CONFIG REWRITE | Persist the running config back into redis.conf |
CONFIG RESETSTAT | Zero the INFO stats and commandstats counters |
redis-cli CONFIG GET 'maxmemory*' redis-cli CONFIG SET maxmemory 2gb maxmemory-policy allkeys-lfu redis-cli CONFIG REWRITE # otherwise the change dies with the process
CONFIG SET is not persistent. Without CONFIG REWRITE the setting is gone on restart,
and CONFIG REWRITE requires that the server was started with a config file. Typical
locations are /etc/redis/redis.conf (Debian and Ubuntu), /etc/redis.conf (RHEL), and
/opt/homebrew/etc/redis.conf (Homebrew on Apple silicon). Confirm with
CONFIG GET dir plus INFO server | grep config_file.
Security
bind 127.0.0.1 -::1 # interfaces to listen on, NOT an allow-list protected-mode yes # refuse external clients when no auth is set port 6379 tls-port 6380 # both can be open, or set port 0 for TLS only requirepass "a-long-random-string" rename-command FLUSHALL "" # legacy blunt instrument, ACLs are better
tls-cert-file /etc/redis/tls/redis.crt tls-key-file /etc/redis/tls/redis.key tls-ca-cert-file /etc/redis/tls/ca.crt tls-auth-clients yes # require client certs
ACLs (6.0+) replace the single shared password with real users.
| Command | What it does |
|---|---|
ACL WHOAMI | The user this connection is authenticated as |
ACL LIST | Every user's rule string |
ACL GETUSER name | One user's flags, keys, channels, and command permissions |
ACL SETUSER name rules... | Create or modify a user |
ACL DELUSER name | Remove a user |
ACL CAT [category] | Command categories, or the commands in one |
ACL GENPASS [bits] | A cryptographically random password |
ACL SAVE / ACL LOAD | Persist to or reload from aclfile |
ACL LOG [count | RESET] | Recent authentication and permission failures |
redis-cli ACL WHOAMI # "default" redis-cli ACL LIST # "user default on nopass sanitize-payload ~* &* +@all" # A read-only app user scoped to one key prefix redis-cli ACL SETUSER hu-reader on '>secret' '~hu:*' '&*' +@read +ping # A worker that may also write its own queues redis-cli ACL SETUSER hu-worker on '>secret2' '~hu:q:*' +@read +@write +@list redis-cli ACL DELUSER hu-reader redis-cli ACL LOG 5
Rule syntax: on/off enables the user, >pass adds a password and nopass allows any,
~pattern grants key access and %R~/%W~ split read from write (7.0+), &pattern
grants pub/sub channels, +cmd/-cmd and +@category/-@category grant and revoke
commands, and allkeys, allchannels, allcommands are the wildcards.
The default default user is nopass ~* &* +@all, which is why an internet-reachable Redis
with no requirepass is fully open. protected-mode yes is the guard that saves you when
you forget, and it only helps until someone sets a password or binds an interface. Never
expose 6379 publicly.
Replication
REPLICAOF 10.0.0.1 6379 # make this node a replica (SLAVEOF is the old name) REPLICAOF NO ONE # promote to a primary INFO replication # role, connected_slaves, offsets, link status WAIT numreplicas timeout_ms # block until N replicas have the write, returns the count
replicaof 10.0.0.1 6379 masterauth "primary-password" masteruser "replication-user" # 6.0+, with ACLs replica-read-only yes # default, and keep it repl-backlog-size 64mb # bigger backlog, more partial resyncs repl-diskless-sync yes min-replicas-to-write 1 # refuse writes with too few replicas min-replicas-max-lag 10
Replication is asynchronous. The primary acknowledges a write before any replica has it,
so a failover can lose acknowledged writes. WAIT 1 100 blocks until at least one replica
confirms and returns how many did, which narrows the window without closing it, because
WAIT is not a consensus protocol and does not participate in failover.
redis-cli INFO replication | grep -E 'role|master_link_status|slave_repl_offset'Replicas are read-only by default. They do not expire keys on their own, they wait for the
primary's DEL, so a read from a replica can briefly return a logically expired value. A
full resync forks and streams an RDB, which is why repl-backlog-size matters: a big enough
backlog turns a brief disconnect into a cheap partial resync instead.
Sentinel
Sentinel gives you automatic failover, not sharding. A separate redis-sentinel process
(run at least three, on distinct hosts) monitors one or more primaries. When a quorum agrees
a primary is down, they elect a leader, promote a replica, and reconfigure the others.
Clients ask a Sentinel for the current primary address rather than hardcoding it, so a
Sentinel-aware client library is mandatory.
# sentinel.conf sentinel monitor hu-primary 10.0.0.1 6379 2 # 2 = quorum sentinel down-after-milliseconds hu-primary 5000 sentinel failover-timeout hu-primary 60000 sentinel parallel-syncs hu-primary 1 sentinel auth-pass hu-primary "primary-password"
redis-sentinel /etc/redis/sentinel.conf redis-cli -p 26379 SENTINEL masters redis-cli -p 26379 SENTINEL get-master-addr-by-name hu-primary redis-cli -p 26379 SENTINEL failover hu-primary # force one, for a drill
Quorum is how many Sentinels must agree the primary is down, and a majority of the Sentinel set must be reachable to actually authorize a failover, which is why an even number is a bad idea. Total memory is still one node's worth, since every replica holds a full copy.
Cluster
Cluster gives you sharding plus failover. The keyspace is divided into 16384 hash
slots, each owned by one primary, and the slot is CRC16(key) mod 16384. Every node knows
the full slot map and redirects a client that asks the wrong one.
redis-cli --cluster create 10.0.0.1:6379 10.0.0.2:6379 10.0.0.3:6379 \ 10.0.0.4:6379 10.0.0.5:6379 10.0.0.6:6379 \ --cluster-replicas 1 redis-cli -c -p 6379 CLUSTER INFO # -c follows redirects redis-cli CLUSTER NODES redis-cli CLUSTER SLOTS # or CLUSTER SHARDS, 7.0+ redis-cli CLUSTER KEYSLOT 'hu:user:1042' redis-cli --cluster check 10.0.0.1:6379 redis-cli --cluster reshard 10.0.0.1:6379
The constraint that shapes your data model is multi-key commands.
(error) CROSSSLOT Keys in request don't hash to the same slotAnything touching several keys at once (MGET, SINTER, ZUNIONSTORE, a Lua script,
MULTI) requires every key to live in the same slot. The fix is a hash tag: only the
substring inside {} is hashed.
CLUSTER KEYSLOT 'hu:{1042}:profile' # same slot as ... CLUSTER KEYSLOT 'hu:{1042}:sessions' # ... this one
Other cluster facts: numbered databases do not exist, so SELECT 1 errors and only db 0 is
available. Regular pub/sub broadcasts to every node, which is why sharded pub/sub
(SPUBLISH) exists. A MOVED redirect means the slot has moved permanently and the client
should update its map, while ASK means a single key is mid-migration. And Cluster is still
asynchronous replication, so a failover can lose writes.
| Standalone | Sentinel | Cluster | |
|---|---|---|---|
| Automatic failover | No | Yes | Yes |
| Sharding | No | No | Yes, 16384 slots |
| Total memory | One node | One node | Sum of the primaries |
| Multi-key commands | Unrestricted | Unrestricted | Same slot only |
| Numbered databases | 16 | 16 | Only db 0 |
| Client support needed | Any | Sentinel-aware | Cluster-aware |
| Minimum nodes | 1 | 3 Sentinels plus 1 primary and 1 replica | 3 primaries, 6 with replicas |
Reach for Sentinel when one node holds your data comfortably and you only want availability. Reach for Cluster when the dataset or the write throughput exceeds one node, and accept the multi-key constraint in exchange.