The Problem
Your Redis operations are slow. Applications timeout on Redis calls, latency spikes appear in monitoring, and users complain about sluggish response times. The slow log shows commands taking milliseconds or even seconds instead of the expected sub-millisecond response times.
Error messages you might see:
Redis::TimeoutError: Connection timed outOr application logs showing:
WARN: Redis command took 2500ms (expected < 10ms)Or slow log entries:
redis-cli SLOWLOG GET 5
1) 1) (integer) 1
2) (integer) 1704067200
3) (integer) 15000000 # 15 seconds!
4) 1) "KEYS"
2) "user:*"Why Redis Gets Slow
Redis is single-threaded for command execution. One slow command blocks all other clients. Common causes:
- 1.O(N) complexity commands - KEYS, SMEMBERS, HGETALL on large datasets
- 2.Large key operations - Deleting or operating on keys with millions of elements
- 3.Full database scans - KEYS pattern matching entire key space
- 4.Network saturation - Large payloads overwhelming bandwidth
- 5.Memory pressure - Redis spending time on eviction and memory management
- 6.Fork-based persistence - BGSAVE causing latency spikes during fork
- 7.Lua script blocking - Long-running scripts blocking all operations
Diagnosis Steps
Enable and Check Slow Log
```bash # Ensure slow log is enabled redis-cli CONFIG GET slowlog-log-slower-than redis-cli CONFIG GET slowlog-max-len
# Set appropriate threshold (10ms = 10000 microseconds) redis-cli CONFIG SET slowlog-log-slower-than 10000 redis-cli CONFIG SET slowlog-max-len 1000
# View recent slow commands redis-cli SLOWLOG GET 20
# Reset for fresh monitoring redis-cli SLOWLOG RESET ```
Check Command Statistics
```bash # View per-command statistics redis-cli INFO commandstats
# Look for commands with high usec_per_call: # cmdstat_keys:calls=50,usec=5000000,usec_per_call=100000 # This shows KEYS averaging 100ms per call
# Find slowest commands redis-cli INFO commandstats | grep -E "usec_per_call=[0-9]{5,}" ```
Identify Large Keys
```bash # Sample large keys redis-cli --bigkeys
# Check specific key sizes redis-cli MEMORY USAGE mykey redis-cli DEBUG OBJECT mykey
# For different data types: redis-cli STRLEN mystring redis-cli HLEN myhash redis-cli LLEN mylist redis-cli SCARD myset redis-cli ZCARD myzset ```
Check Latency Spikes
```bash # Measure network latency to Redis redis-cli --latency
# Historical latency redis-cli --latency-history
# Check intrinsic latency (Redis internal) redis-cli --intrinsic-latency 10 ```
Monitor Memory Impact
```bash # Check if memory pressure causes latency redis-cli INFO memory | grep -E "used_memory|maxmemory|mem_fragmentation_ratio"
# Check eviction activity redis-cli INFO stats | grep evicted_keys ```
Solutions
Solution 1: Replace KEYS with SCAN
The KEYS command scans the entire key space, blocking Redis. Use SCAN instead:
```bash # BAD: Blocks Redis for entire database scan redis-cli KEYS "user:*"
# GOOD: Incremental scan, non-blocking redis-cli SCAN 0 MATCH "user:*" COUNT 100 ```
In application code:
```javascript // Node.js - safe key iteration async function findKeys(pattern) { const keys = []; let cursor = '0';
do { const [nextCursor, batch] = await redis.scan( cursor, 'MATCH', pattern, 'COUNT', 100 ); cursor = nextCursor; keys.push(...batch); } while (cursor !== '0');
return keys; } ```
# Python - safe key iteration
def find_keys(pattern):
keys = []
cursor = 0
while cursor != 0:
cursor, batch = r.scan(cursor=cursor, match=pattern, count=100)
keys.extend(batch)
return keysSolution 2: Avoid Large Range Operations
Fetching entire collections causes O(N) delays:
```bash # BAD: Returns all 100,000 elements, blocks for seconds redis-cli LRANGE mylist 0 -1 redis-cli SMEMBERS myset redis-cli HGETALL myhash
# GOOD: Paginate or use SCAN redis-cli LRANGE mylist 0 99 # First 100 redis-cli SSCAN myset 0 COUNT 100 redis-cli HSCAN myhash 0 ```
For specific fields, fetch only what you need:
# Instead of HGETALL (all fields)
redis-cli HGET myhash field1
redis-cli HMGET myhash field1 field2 field3Solution 3: Use UNLINK Instead of DEL
Deleting large keys synchronously blocks Redis:
```bash # BAD: Blocks while freeing memory redis-cli DEL biglist
# GOOD: Async deletion in background redis-cli UNLINK biglist ```
Enable lazy deletion globally:
redis-cli CONFIG SET lazyfree-lazy-eviction yes
redis-cli CONFIG SET lazyfree-lazy-expire yes
redis-cli CONFIG SET lazyfree-lazy-server-del yesSolution 4: Disable Dangerous Commands
Prevent accidental use of slow commands:
```bash # In redis.conf - rename to empty string to disable rename-command KEYS "" rename-command FLUSHALL "" rename-command FLUSHDB ""
# Or rename to obscure names for admin use rename-command KEYS "KEYS_ADMIN_ONLY_7x9k2" ```
Solution 5: Optimize Lua Scripts
Long Lua scripts block everything. Keep scripts short:
```lua -- BAD: Processes 10000 items in single call local items = redis.call('LRANGE', KEYS[1], 0, 10000) for i, item in ipairs(items) do redis.call('HSET', 'result', item, i) end
-- GOOD: Process in batches, call repeatedly local start = tonumber(ARGV[1]) local end = start + 99 local items = redis.call('LRANGE', KEYS[1], start, end) for i, item in ipairs(items) do redis.call('HSET', 'result', item, start + i) end return start + 100 ```
Solution 6: Fix Fork-Based Latency
BGSAVE and BGREWRITEAOF fork the process, causing latency spikes:
```bash # Check if fork is causing latency redis-cli INFO persistence | grep rdb_last_bgsave_status redis-cli INFO stats | grep fork
# Solutions: # 1. Enable overcommit memory sudo sysctl vm.overcommit_memory=1
# 2. Disable THP sudo echo never > /sys/kernel/mm/transparent_hugepage/enabled
# 3. Use diskless replication redis-cli CONFIG SET repl-diskless-sync yes
# 4. Reduce save frequency if acceptable redis-cli CONFIG SET save "900 100" ```
Solution 7: Use Pipelining for Batch Operations
Reduce network round trips:
// Node.js - pipeline multiple commands
const pipeline = redis.pipeline();
for (let i = 0; i < 1000; i++) {
pipeline.set(`key:${i}`, `value:${i}`);
}
await pipeline.exec();# Python - pipeline
pipe = r.pipeline()
for i in range(1000):
pipe.set(f'key:{i}', f'value:{i}')
pipe.execute()Solution 8: Optimize Data Structures
Choose efficient structures for your use case:
```bash # Use hashes for small objects (memory efficient) # Instead of: SET user:1:name "John" SET user:1:email "john@example.com"
# Use: HMSET user:1 name "John" email "john@example.com"
# Use sorted sets for ordered data with efficient range queries ZADD leaderboard 100 "player1" 95 "player2" ZREVRANGE leaderboard 0 9 WITHSCORES # Top 10, O(log N)
# Use sets for membership testing SISMEMBER allowed_users "user123" # O(1) ```
Solution 9: Scale Read Operations
For read-heavy workloads, use replicas:
```bash # Check if read load is the issue redis-cli INFO stats | grep instantaneous_ops_per_sec
# Add replicas for read scaling # Application reads from replica, writes to master ```
Monitoring Configuration
```ini # /etc/redis/redis.conf
# Slow log slowlog-log-slower-than 10000 slowlog-max-len 128
# Lazy deletion lazyfree-lazy-eviction yes lazyfree-lazy-expire yes lazyfree-lazy-server-del yes replica-lazy-flush yes
# Disable dangerous commands rename-command KEYS "" rename-command FLUSHALL ""
# Memory optimization maxmemory-policy allkeys-lru
# Persistence tuning save 900 100 stop-writes-on-bgsave-error no ```
Monitoring Script
```bash #!/bin/bash # slow_query_monitor.sh
SLOW_THRESHOLD=10 # Number of slow commands to alert
SLOW_COUNT=$(redis-cli SLOWLOG LEN)
if [ "$SLOW_COUNT" -gt "$SLOW_THRESHOLD" ]; then echo "WARNING: $SLOW_COUNT slow commands detected" redis-cli SLOWLOG GET 10 fi
# Check for high latency commands redis-cli INFO commandstats | while read line; do usec=$(echo "$line" | grep -oP 'usec_per_call=\K[0-9]+') if [ "$usec" -gt 50000 ]; then echo "HIGH LATENCY: $line" fi done ```
Prevention Checklist
- [ ] Never use KEYS in production applications
- [ ] Use SCAN for all key iteration
- [ ] Paginate large collection operations
- [ ] Use UNLINK instead of DEL for large keys
- [ ] Keep Lua scripts short and atomic
- [ ] Enable lazy deletion settings
- [ ] Monitor slow log continuously
- [ ] Configure appropriate slow log threshold
- [ ] Disable or rename dangerous commands
- [ ] Use pipelining for batch operations
- [ ] Choose appropriate data structures
Related Issues
- [Redis Out of Memory](./fix-redis-out-of-memory)
- [Redis Connection Refused](./fix-redis-connection-refused)
- [Redis Persistence Failed](./fix-redis-persistence-failed)