Software Engineering Wiki

Data

Redis

Data structures and their costs, persistence and eviction behaviour, keyspace inspection and the commands for a slow or full instance.

Cheatsheet #

TaskCommand
Connectredis-cli -h host -p 6379 --user u --pass p --tls
Server summaryredis-cli info server|memory|stats|replication
Live command streamredis-cli monitor (never on a busy production node)
Slow queriesredis-cli slowlog get 10
Safe key scanredis-cli --scan --pattern 'session:*' --count 1000
Biggest keysredis-cli --bigkeys
Memory of one keyredis-cli memory usage mykey
Keyspace summaryredis-cli info keyspace
Latency checkredis-cli --latency
Which keys are expiringredis-cli info stats | grep expired
Cluster healthredis-cli --cluster check host:6379
Replication lagredis-cli info replication
Persist nowredis-cli bgsave
Flush one databaseredis-cli -n 3 flushdb

keys *, flushall and monitor on production

keys and flushall block the single-threaded event loop for the whole scan; monitor mirrors every command to your terminal and can halve throughput. Use --scan, flushdb async, and the slowlog instead.

The model #

Redis executes commands one at a time on a single thread (networking is threaded, execution is not). That is why it is predictable and why one O(n) command against a million-element key stalls every other client. Latency problems are almost always a command whose cost is proportional to data size, or a fork for persistence.

redis-cli info stats | grep -E 'instantaneous_ops|keyspace_(hits|misses)|evicted|expired'
redis-cli --latency-history -i 5
redis-cli slowlog get 10
redis-cli info commandstats | sort -t= -k2 -rn | head

Data structures and their costs #

TypeUseWatch out for
StringCounters, cached blobs, flagsappend grows in place; large values block on transfer
HashObject fields addressed individuallySmall hashes are memory-efficient (listpack); large ones are not
ListQueues, recent-Nlrange 0 -1 is O(n); push/pop are O(1)
SetMembership, tagssmembers on a big set is O(n); use sscan
Sorted setLeaderboards, time indexes, rate limitszrangebyscore with a bound is cheap, unbounded is not
StreamEvent log with consumer groupsTrim it: xadd ... maxlen ~ 100000
Bitmap / HyperLogLogCardinality at fixed costApproximate by design
redis-cli set session:123 '{"u":7}' ex 3600 nx        # set with TTL, only if absent
redis-cli hset user:7 name jodis email j@example.com
redis-cli lpush jobs '{"id":1}' && redis-cli brpop jobs 5
redis-cli zadd scores 42 alice && redis-cli zrevrange scores 0 9 withscores
redis-cli xadd events '*' type deploy service api
redis-cli setex lock:deploy 30 "$(hostname)"          # crude lock; see below

TTL and eviction #

Keys expire lazily (on access) and through a background sampler, so an expired key can still occupy memory for a while. When maxmemory is reached, the eviction policy decides what happens — including refusing writes.

redis-cli config get maxmemory maxmemory-policy
redis-cli config set maxmemory-policy allkeys-lru
redis-cli ttl session:123          # -1 = no expiry, -2 = gone
redis-cli expire session:123 600
redis-cli persist session:123      # remove the TTL
PolicyBehaviour
noevictionWrites fail with an error once full — correct for a queue or a store of record
allkeys-lruEvict least recently used from all keys — the cache default
volatile-lruEvict only keys with a TTL; if none have one, writes fail
allkeys-lfuFrequency-based: better for skewed access patterns

volatile-* policies with no TTLs set is a common trap: the instance fills, nothing is evictable, and writes start failing while memory looks “available”.

Persistence #

ModeWhat survives a crashCost
RDB snapshotEverything up to the last snapshotFork and write, periodic
AOF (appendfsync everysec)Up to one second of writesContinuous, rewrites periodically
BothAOF replays after the snapshotRecommended when data matters
NeitherNothingPure cache
redis-cli config get save appendonly appendfsync
redis-cli bgsave && redis-cli info persistence | grep -E 'rdb_last_bgsave_status|aof_last_write_status'
redis-cli bgrewriteaof

The fork for a background save copies page tables, so a large instance pauses briefly and may double memory under heavy writes. Keep maxmemory at roughly half of RAM if persistence is enabled, and make sure vm.overcommit_memory=1 or the fork can fail outright.

Keyspace inspection #

redis-cli --scan --pattern 'session:*' --count 1000 | head
redis-cli --bigkeys                     # one pass, largest key per type
redis-cli --memkeys                     # sampling by memory (Redis 6.2+)
redis-cli memory usage session:123
redis-cli object encoding user:7        # listpack, hashtable, intset, skiplist...
redis-cli info keyspace
redis-cli dbsize

object encoding reveals whether a structure is still in its compact form. A hash that crossed hash-max-listpack-entries silently becomes several times larger.

Locks and atomicity #

setnx with a TTL is a lock only if the release checks ownership; otherwise a slow holder deletes someone else’s lock. Do the check and delete in one Lua script, which runs atomically.

redis-cli set lock:deploy "$TOKEN" nx ex 30
-- release.lua: delete only if we still own it
if redis.call("get", KEYS[1]) == ARGV[1] then
  return redis.call("del", KEYS[1])
end
return 0
redis-cli --eval release.lua lock:deploy , "$TOKEN"

For anything where a lost lock causes real damage, use a proper coordination service; single-node Redis locks are lost with the node.

Replication and cluster #

redis-cli info replication                     # role, offsets, connected replicas
redis-cli --cluster check 10.0.0.5:6379
redis-cli --cluster info 10.0.0.5:6379
redis-cli cluster nodes | awk '{print $2, $3, $9}'
redis-cli -c -h 10.0.0.5 get user:7            # -c follows MOVED redirects

Replication is asynchronous: a write acknowledged by the primary may not exist on a replica yet, and a failover can lose it. wait 1 100 blocks until one replica confirms, which reduces but does not eliminate the window.

In cluster mode, multi-key operations must stay in one hash slot. Use hash tags — {user:7}:profile and {user:7}:sessions share a slot.

Troubleshooting #

SymptomCause
Periodic latency spikesbgsave/bgrewriteaof fork, or transparent huge pages
One client stalls everythingAn O(n) command: keys, smembers, lrange 0 -1 on a big key
OOM command not allowedmaxmemory reached with noeviction or nothing evictable
Hit rate fallingEviction pressure or TTLs too short — check evicted_keys
MOVED/ASK errorsCluster client not in cluster mode (-c)
Replica far behindNetwork, or a large write burst; check master_repl_offset versus replica offset
Connections refused under loadmaxclients, or file descriptor limits on the unit
redis-cli info clients | grep -E 'connected_clients|blocked_clients|maxclients'
redis-cli client list | awk '{print $2, $6, $12}' | head
redis-cli client kill id 42
cat /sys/kernel/mm/transparent_hugepage/enabled     # should be [never] for Redis

Oneliners #

# Key count by prefix, without blocking
redis-cli --scan --count 1000 | awk -F: '{print $1}' | sort | uniq -c | sort -rn | head

# Keys with no TTL (candidates for a leak)
redis-cli --scan --pattern 'cache:*' | while read -r k; do [ "$(redis-cli ttl "$k")" = -1 ] && echo "$k"; done | head

# Memory by prefix, sampled
redis-cli --scan --count 500 | head -2000 | xargs -n1 -I{} sh -c 'printf "%s %s\n" "$(redis-cli memory usage {})" "{}"' | sort -rn | head

# Cache hit ratio
redis-cli info stats | awk -F: '/keyspace_hits|keyspace_misses/ {a[$1]=$2} END {printf "%.2f%%\n", 100*a["keyspace_hits"]/(a["keyspace_hits"]+a["keyspace_misses"])}'

# Top commands by call count
redis-cli info commandstats | sed 's/cmdstat_//' | sort -t= -k2 -rn | head

# Delete a pattern safely, in batches
redis-cli --scan --pattern 'tmp:*' | xargs -L 500 redis-cli del

# Copy a key to another instance, server side
redis-cli migrate target-host 6379 mykey 0 5000 copy replace

# Watch operations per second
redis-cli --stat

# Confirm a failover target is healthy before promoting
redis-cli -h replica info replication | grep -E 'master_link_status|slave_read_only'

# Export all keys matching a pattern as JSON lines
redis-cli --scan --pattern 'user:*' | while read -r k; do printf '{"key":"%s","value":%s}\n' "$k" "$(redis-cli --raw get "$k")"; done

# Benchmark a realistic mix
redis-benchmark -t get,set -n 100000 -P 16 -q

Last updated 15 September 2026 · Edit this page