Data
Redis
Data structures and their costs, persistence and eviction behaviour, keyspace inspection and the commands for a slow or full instance.
Cheatsheet #
| Task | Command |
|---|---|
| Connect | redis-cli -h host -p 6379 --user u --pass p --tls |
| Server summary | redis-cli info server|memory|stats|replication |
| Live command stream | redis-cli monitor (never on a busy production node) |
| Slow queries | redis-cli slowlog get 10 |
| Safe key scan | redis-cli --scan --pattern 'session:*' --count 1000 |
| Biggest keys | redis-cli --bigkeys |
| Memory of one key | redis-cli memory usage mykey |
| Keyspace summary | redis-cli info keyspace |
| Latency check | redis-cli --latency |
| Which keys are expiring | redis-cli info stats | grep expired |
| Cluster health | redis-cli --cluster check host:6379 |
| Replication lag | redis-cli info replication |
| Persist now | redis-cli bgsave |
| Flush one database | redis-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 | headData structures and their costs #
| Type | Use | Watch out for |
|---|---|---|
| String | Counters, cached blobs, flags | append grows in place; large values block on transfer |
| Hash | Object fields addressed individually | Small hashes are memory-efficient (listpack); large ones are not |
| List | Queues, recent-N | lrange 0 -1 is O(n); push/pop are O(1) |
| Set | Membership, tags | smembers on a big set is O(n); use sscan |
| Sorted set | Leaderboards, time indexes, rate limits | zrangebyscore with a bound is cheap, unbounded is not |
| Stream | Event log with consumer groups | Trim it: xadd ... maxlen ~ 100000 |
| Bitmap / HyperLogLog | Cardinality at fixed cost | Approximate 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 belowTTL 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| Policy | Behaviour |
|---|---|
noeviction | Writes fail with an error once full — correct for a queue or a store of record |
allkeys-lru | Evict least recently used from all keys — the cache default |
volatile-lru | Evict only keys with a TTL; if none have one, writes fail |
allkeys-lfu | Frequency-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 #
| Mode | What survives a crash | Cost |
|---|---|---|
| RDB snapshot | Everything up to the last snapshot | Fork and write, periodic |
AOF (appendfsync everysec) | Up to one second of writes | Continuous, rewrites periodically |
| Both | AOF replays after the snapshot | Recommended when data matters |
| Neither | Nothing | Pure 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 bgrewriteaofThe 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 dbsizeobject 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 0redis-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 redirectsReplication 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 #
| Symptom | Cause |
|---|---|
| Periodic latency spikes | bgsave/bgrewriteaof fork, or transparent huge pages |
| One client stalls everything | An O(n) command: keys, smembers, lrange 0 -1 on a big key |
OOM command not allowed | maxmemory reached with noeviction or nothing evictable |
| Hit rate falling | Eviction pressure or TTLs too short — check evicted_keys |
MOVED/ASK errors | Cluster client not in cluster mode (-c) |
| Replica far behind | Network, or a large write burst; check master_repl_offset versus replica offset |
| Connections refused under load | maxclients, 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 RedisOneliners #
# 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