Redis Ops

Running Redis safely: install, tune redis.conf, choose RDB vs AOF, secure with ACLs, replicate, and pick between Sentinel and Cluster. Plus the single most common production mistake.

If you only remember six things
  • Set maxmemory. Set maxmemory-policy. Redis without a cap is a time bomb.
  • Never run KEYS * in production. Use SCAN. Every time.
  • RDB is a snapshot; AOF is a log. Different recovery characteristics — know which you have.
  • Sentinel = HA for a single shard. Cluster = sharded + HA. They are not interchangeable.
  • Authentication: ACLs (Redis 6+). Never requirepass alone and never shared.
  • Pipelining beats round-trips. Use it or lose half your throughput to network latency.

Install

# EL9 — ships a recent Redis in AppStream:
sudo dnf install -y redis
sudo systemctl enable --now redis

# Verify:
redis-cli ping       # PONG
redis-cli info server | head

On Debian/Ubuntu the package is redis-server and the service is redis-server. On EL the service and config live at /etc/redis/redis.conf (or /etc/redis.conf on older builds).

redis.conf: the knobs that matter

# /etc/redis/redis.conf (excerpt)
bind 127.0.0.1 10.0.0.7                   # never 0.0.0.0 without auth AND a firewall
port 6379
protected-mode yes                        # refuses remote connections without a password

# --- Memory ---
maxmemory 12gb
maxmemory-policy allkeys-lru              # see the eviction table below

# --- Persistence ---
save 3600 1
save 300 100
save 60 10000
appendonly yes
appendfsync everysec                      # good default
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# --- Replication ---
replica-read-only yes
repl-backlog-size 128mb
replica-serve-stale-data yes

# --- Security ---
requirepass redacted                      # legacy; prefer ACL users below
aclfile /etc/redis/users.acl
rename-command FLUSHDB ""                 # disable destructive commands entirely
rename-command FLUSHALL ""
rename-command CONFIG ""                  # or rename to an obscure string

# --- Clients ---
timeout 0
tcp-keepalive 60
maxclients 10000

# --- Logging ---
loglevel notice
logfile /var/log/redis/redis.log
Defense in depth. protected-mode yes, bind to specific interfaces, require auth, firewall to specific subnets. Redis is a remote code execution engine pretending to be a cache — DEBUG, CONFIG, and MODULE LOAD are all dangerous if an attacker reaches the port.

Persistence: RDB vs AOF

RDBAOF
What it isPoint-in-time binary snapshotAppend-only log of every write
Restore timeFast (load one file)Slower (replay all commands)
Worst-case data lossMinutes (whatever's between snapshots)≤ 1 second with everysec
Disk footprintSmallGrows, then rewrites when large
Fork costOccasional big forkOccasional rewrite fork
Good forCold backups, warm-up after restartDurable writes you can't afford to lose

Production answer: run both. RDB snapshots go to backup storage; AOF bounds live data-loss to one second. Tune appendfsync:

# Trigger an RDB snapshot right now, non-blocking:
redis-cli BGSAVE
# AOF rewrite on demand (compacts the log):
redis-cli BGREWRITEAOF

Authentication & ACLs

Redis 6 introduced ACLs: per-user passwords, command whitelists, and key-pattern restrictions. Use them.

# Inside redis-cli as a CONFIG-capable user:
ACL SETUSER app_rw on >redacted ~appcache:* &* +@read +@write -@dangerous
ACL SETUSER app_ro on >redacted ~appcache:* +@read
ACL SETUSER admin  on >redacted ~* &* +@all
ACL SAVE             # persists to aclfile

# From a shell:
redis-cli --user app_rw --pass 'redacted' SET key value

Pub/Sub, Streams, and everyday data types

# Strings
SET user:42:name "Jane"
INCR pageviews
EXPIRE user:42:name 3600

# Hashes (small objects, field-level access)
HSET user:42 name "Jane" email "jane@example.com" tier "pro"
HGET user:42 email

# Lists (queues, recent-X feeds)
LPUSH jobs '{"id":1,"op":"thumb"}'
BRPOP jobs 30                             # blocking pop

# Sets / sorted sets
SADD followers:42 17 29 31
ZADD leaderboard 2500 player:1 2450 player:2
ZRANGEBYSCORE leaderboard 2000 +inf WITHSCORES

# Pub/Sub (fire-and-forget; no persistence)
SUBSCRIBE events
PUBLISH events '{"user":42,"type":"login"}'

# Streams — persistent, consumer groups, like Kafka-lite
XADD orders '*' id 7 total 42.50
XGROUP CREATE orders workers '$' MKSTREAM
XREADGROUP GROUP workers worker1 COUNT 10 BLOCK 5000 STREAMS orders '>'
XACK orders workers 1700000000-0
Pub/Sub has no persistence, no delivery guarantees, no consumer groups. If a subscriber is offline, it misses messages. Use Streams when you need either persistence or work-queue semantics.

Replication with replicaof

# On the replica:
replicaof primary.redis 6379
masterauth redacted                       # required if primary has requirepass
replica-read-only yes
redis-cli INFO replication
# On a primary:
#   role:master
#   connected_slaves:2
#   slave0:ip=10.0.0.8,port=6379,state=online,offset=12345,lag=0
# On a replica:
#   role:slave
#   master_link_status:up
#   master_last_io_seconds_ago:0
#   master_sync_in_progress:0

Replication is asynchronous. A primary can ack a write before any replica has received it — a primary-crash window of lost writes exists. For stronger durability use WAIT numreplicas timeout:

redis-cli SET important "value"
redis-cli WAIT 1 500        # wait up to 500ms for 1 replica to confirm

Sentinel: HA for a single shard

Sentinel is a companion process that watches a primary + its replicas. When a quorum agrees the primary is down, Sentinel promotes a replica and tells clients the new address.

# /etc/redis/sentinel.conf (on each of 3+ Sentinel nodes)
port 26379
sentinel monitor mymaster 10.0.0.7 6379 2     # quorum = 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel auth-pass mymaster redacted
redis-sentinel /etc/redis/sentinel.conf
# Ask Sentinel for the current primary:
redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster

Clients must be Sentinel-aware (most language clients are). They query Sentinel for the current primary, not a hard-coded host.

Cluster: sharding with hash slots

Redis Cluster partitions the keyspace into 16384 hash slots. Each key's slot is CRC16(key) mod 16384. Each node owns a range of slots; replicas shadow each primary.

# On every node:
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
appendonly yes
# Bootstrap a 6-node cluster (3 primaries, 3 replicas):
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

# Inspect:
redis-cli -c -h 10.0.0.1 CLUSTER NODES
redis-cli -c -h 10.0.0.1 CLUSTER INFO
Multi-key commands across slots fail. MGET k1 k2 only works if both keys land in the same slot. Force co-location with a hash tag: the substring between { and } is the only part hashed, so user:{42}:name and user:{42}:email share a slot.

Eviction

When Redis hits maxmemory it applies the eviction policy to make room. If the policy is noeviction (default!) writes start failing with OOM errors.

PolicyBehaviourWhen to use
noevictionReturn errors on write when fullWhen Redis is the system of record, never a cache
allkeys-lruEvict least-recently-used key, any keyGeneral-purpose cache. Probably what you want.
allkeys-lfuEvict least-frequently-used keyHot-set workloads where recency lies
volatile-lruLRU among keys with a TTLMixed: persistent keys (no TTL) and cache keys (TTL)
allkeys-randomEvict a random keyRarely
volatile-ttlEvict the key expiring soonestQueue-like workloads

Monitoring & diagnostics

# Top-level:
redis-cli INFO                           # sectioned; pipe to grep
redis-cli INFO memory
redis-cli INFO replication
redis-cli INFO stats

# Real-time:
redis-cli --stat                         # per-second summary
redis-cli --latency                      # ping latency to the server
redis-cli --latency-history -i 5         # long-run latency

# Big-key hunt (never in peak hours):
redis-cli --bigkeys                      # samples; not exhaustive
redis-cli --memkeys                      # sorts by memory

# Per-key memory:
redis-cli MEMORY USAGE appcache:user:42 SAMPLES 0

# Slow log:
redis-cli CONFIG SET slowlog-log-slower-than 10000   # microseconds, so 10ms
redis-cli SLOWLOG GET 20
redis-cli SLOWLOG RESET

# Trace what's happening RIGHT NOW (expensive!):
redis-cli MONITOR                        # prints every command; use for seconds, not minutes

Common pitfalls

Anti-patternWhy it bitesBetter
KEYS * in productionO(N) scan of every key, blocks the single threadSCAN 0 MATCH pattern COUNT 500, in a loop
Storing 5 MB valuesOne big key blocks the event loop during serialise/replicateChunk the data; Redis likes millions of small values
No maxmemory capOS OOM kills RedisSet it to ~80% of available RAM
Trusting EXPIRE for precise TTLExpiry is lazy + sampled, not real-timeFine for caches; don't use for scheduling
Using pub/sub as a durable busOffline subscriber = lost messagesUse Streams with consumer groups
Cluster + multi-key ops without hash tagsCROSSSLOT errorsUse {tag} to co-locate
No TLS on cross-subnet trafficPlaintext auth over the networktls-port, tls-cert-file, tls-key-file, tls-ca-cert-file
Running DEBUG SLEEP or SAVE on a live primaryBlocks every client for the durationBGSAVE, and rename DEBUG out of existence