What's Actually Happening
Redis memory usage grows continuously and reaches high levels. Memory is not being freed, potentially causing performance issues or crashes.
The Error You'll See
```bash $ redis-cli INFO memory
used_memory: 8589934592 used_memory_human: 8.00G maxmemory: 10737418240 maxmemory_human: 10.00G ```
Memory warning:
WARNING: Redis is using more than 90% of configured maxmemoryOOM error:
OOM command not allowed when used memory > 'maxmemory'Eviction errors:
Error: MISCONF Redis is configured to save RDB snapshotsMemory fragmentation:
```bash $ redis-cli INFO memory
mem_fragmentation_ratio: 2.5 ```
Why This Happens
- 1.Keys without expiration - Keys never expire, accumulate over time
- 2.Large values stored - Large strings, lists, or sets consuming memory
- 3.Wrong eviction policy - No-eviction policy doesn't remove keys
- 4.Memory fragmentation - Allocator fragmentation overhead
- 5.No maxmemory limit - Redis can use unlimited memory
- 6.Client buffers - Large client output buffers
Step 1: Check Redis Memory Stats
```bash # Check memory usage: redis-cli INFO memory
# Key metrics: # used_memory - Current memory usage in bytes # used_memory_human - Human readable usage # used_memory_peak - Peak memory usage # maxmemory - Configured limit # mem_fragmentation_ratio - Fragmentation overhead
# Check memory usage percentage: redis-cli INFO memory | grep -E "used_memory_human|maxmemory_human"
# Check peak memory: redis-cli INFO memory | grep used_memory_peak_human
# Calculate usage percentage: redis-cli --eval memory-percent.lua
# Check fragmentation ratio: redis-cli INFO memory | grep mem_fragmentation_ratio # Should be around 1.0-1.5 # > 1.5 indicates significant fragmentation
# Check total system memory: free -h ```
Step 2: Identify Memory Consumers
```bash # Find largest keys: redis-cli --bigkeys
# Sample output: # [00.00%] Biggest string found 'key1' has 10000 bytes # [50.00%] Biggest list found 'list1' has 5000 items
# Find keys by pattern: redis-cli KEYS "session:*" | head -100
# Count keys by pattern: redis-cli --eval count-keys.lua , "session:*"
# Check specific key size: redis-cli DEBUG OBJECT key1 # Or: STRLEN, LLEN, HLEN, SCARD depending on type
# Get key memory usage: redis-cli MEMORY USAGE key1
# Analyze key types: redis-cli --eval analyze-keys.lua
# Check database size: redis-cli DBSIZE ```
Step 3: Check Key Expiration
```bash # Check keys with TTL: redis-cli TTL key1 # -1 = no expiration (never expire) # -2 = key doesn't exist # > 0 = seconds until expiration
# Find keys without expiration: redis-cli --scan | while read key; do ttl=$(redis-cli TTL "$key") if [ "$ttl" == "-1" ]; then echo "$key" fi done
# Set expiration on keys: redis-cli EXPIRE key1 3600
# Set expiration in milliseconds: redis-cli PEXPIRE key1 3600000
# Set expiration at specific time: redis-cli EXPIREAT key1 1704067200
# Check expiration stats: redis-cli INFO stats | grep expired_keys
# Keys expiring per second: redis-cli --eval expiration-rate.lua ```
Step 4: Configure Eviction Policy
```bash # Check current policy: redis-cli CONFIG GET maxmemory-policy
# Available policies: # noeviction - Don't evict, return errors (default) # allkeys-lru - Evict least recently used keys # volatile-lru - Evict LRU among keys with expiration # allkeys-lfu - Evict least frequently used keys # volatile-lfu - Evict LFU among keys with expiration # allkeys-random - Evict random keys # volatile-random - Evict random keys with expiration # volatile-ttl - Evict keys with shortest TTL
# Set eviction policy: redis-cli CONFIG SET maxmemory-policy allkeys-lru
# For temporary data (sessions, cache): redis-cli CONFIG SET maxmemory-policy volatile-lru
# Set maxmemory limit: redis-cli CONFIG SET maxmemory 4gb
# Or in redis.conf: maxmemory 4gb maxmemory-policy allkeys-lru
# Restart Redis if needed: systemctl restart redis ```
Step 5: Set Key Expiration on Write
```bash # Set key with expiration: redis-cli SET key1 value1 EX 3600
# Or separate commands: redis-cli SET key1 value1 redis-cli EXPIRE key1 3600
# For hashes: redis-cli HSET hash1 field1 value1 redis-cli EXPIRE hash1 3600
# For lists: redis-cli LPUSH list1 item1 redis-cli EXPIRE list1 3600
# For sets: redis-cli SADD set1 member1 redis-cli EXPIRE set1 3600
# Application code pattern: # Python: r.set('session:user1', data, ex=3600)
# Node.js: client.set('session:user1', data, 'EX', 3600)
# Always set TTL for cache/session data ```
Step 6: Analyze Key Types
```bash # Check key type: redis-cli TYPE key1
# Types: string, list, set, zset, hash, stream
# Get size by type: # String: redis-cli STRLEN key1
# List: redis-cli LLEN key1
# Hash: redis-cli HLEN key1
# Set: redis-cli SCARD key1
# Sorted Set: redis-cli ZCARD key1
# Estimate memory for each type: # String: ~100 bytes overhead + value size # List: ~100 bytes overhead + 64 bytes per item # Hash: ~100 bytes overhead + 64 bytes per field # Set: ~100 bytes overhead + 64 bytes per member
# Check encoding: redis-cli OBJECT ENCODING key1 # Can be: raw, embstr, int, ziplist, hashtable, etc. ```
Step 7: Clean Up Unused Keys
```bash # Delete keys by pattern: redis-cli --scan --pattern "old:*" | xargs redis-cli DEL
# Delete keys without expiration: redis-cli --scan | while read key; do ttl=$(redis-cli TTL "$key") if [ "$ttl" == "-1" ]; then redis-cli DEL "$key" fi done
# Delete large keys: redis-cli --bigkeys | grep "Biggest" | awk '{print $4}' | tr "'" "" | xargs redis-cli DEL
# Delete old sessions: redis-cli --scan --pattern "session:*" | while read key; do redis-cli DEL "$key" done
# Use UNLINK for async deletion (Redis 4.0+): redis-cli --scan --pattern "temp:*" | xargs redis-cli UNLINK
# Flush specific database: redis-cli -n 1 FLUSHDB
# Note: FLUSHALL deletes all databases ```
Step 8: Configure Memory Limits
```bash # In redis.conf:
# Set max memory: maxmemory 4gb
# Set eviction policy: maxmemory-policy allkeys-lru
# Set sample size for eviction: maxmemory-samples 5
# Disable persistence if not needed: save "" # Or reduce frequency: save 900 1 save 300 10 save 60 10000
# Disable replicas if not needed: replicaof no one
# Reduce client buffers: client-output-buffer-limit normal 0 0 0 client-output-buffer-limit replica 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60
# Apply changes: redis-cli CONFIG REWRITE ```
Step 9: Check Client Buffers
```bash # Check client list: redis-cli CLIENT LIST
# Check output buffer sizes: redis-cli CLIENT LIST | grep -E "omem|qmem"
# Check blocked clients: redis-cli CLIENT LIST | grep blocked
# Kill clients with large buffers: redis-cli CLIENT LIST | while read line; do omem=$(echo $line | grep -oP 'omem=\K\d+') if [ "$omem" -gt 10000000 ]; then id=$(echo $line | grep -oP 'id=\K\d+') redis-cli CLIENT KILL ID $id fi done
# Check pub/sub subscribers: redis-cli PUBSUB CHANNELS redis-cli PUBSUB NUMSUB channel1
# Configure buffer limits: redis-cli CONFIG SET client-output-buffer-limit "normal 0 0 0" redis-cli CONFIG SET client-output-buffer-limit "replica 256mb 64mb 60" ```
Step 10: Redis Memory Verification Script
```bash # Create verification script: cat << 'EOF' > /usr/local/bin/check-redis-memory.sh #!/bin/bash
REDIS_HOST=${1:-"localhost"} REDIS_PORT=${2:-"6379"}
echo "=== Memory Usage ===" redis-cli -h $REDIS_HOST -p $REDIS_PORT INFO memory | grep -E "used_memory_human|used_memory_peak_human|maxmemory_human|mem_fragmentation_ratio"
echo "" echo "=== Memory Limits ===" redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET maxmemory redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET maxmemory-policy
echo "" echo "=== Key Statistics ===" redis-cli -h $REDIS_HOST -p $REDIS_PORT DBSIZE redis-cli -h $REDIS_HOST -p $REDIS_PORT INFO stats | grep -E "expired_keys|evicted_keys"
echo "" echo "=== Biggest Keys ===" redis-cli -h $REDIS_HOST -p $REDIS_PORT --bigkeys
echo "" echo "=== Keys Without Expiration ===" count=0 redis-cli -h $REDIS_HOST -p $REDIS_PORT --scan | head -100 | while read key; do ttl=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT TTL "$key") if [ "$ttl" == "-1" ]; then echo "$key (no TTL)" count=$((count + 1)) fi done | head -20 echo "Total checked: 100"
echo "" echo "=== Client Buffers ===" redis-cli -h $REDIS_HOST -p $REDIS_PORT CLIENT LIST | awk '{for(i=1;i<=NF;i++) if($i~/^(id|omem|qmem)=/) print $i}' | head -20
echo "" echo "=== Persistence Config ===" redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET save EOF
chmod +x /usr/local/bin/check-redis-memory.sh
# Usage: /usr/local/bin/check-redis-memory.sh localhost 6379
# Monitor: watch -n 30 /usr/local/bin/check-redis-memory.sh ```
Redis Memory Checklist
| Check | Command | Expected |
|---|---|---|
| Memory usage | INFO memory | Below maxmemory |
| Fragmentation | mem_fragmentation_ratio | < 1.5 |
| Eviction policy | CONFIG GET maxmemory-policy | Appropriate for use case |
| Key expiration | TTL keys | Keys have TTL for cache |
| Client buffers | CLIENT LIST | No large buffers |
Verify the Fix
```bash # After fixing memory usage
# 1. Check memory usage redis-cli INFO memory | grep used_memory_human // Below maxmemory
# 2. Check fragmentation redis-cli INFO memory | grep mem_fragmentation_ratio // Around 1.0-1.5
# 3. Monitor over time # Wait 1 hour redis-cli INFO memory | grep used_memory_human // Stable or declining
# 4. Check evicted keys redis-cli INFO stats | grep evicted_keys // Eviction working if needed
# 5. Check TTL on keys redis-cli TTL session:user1 // Has expiration
# 6. Verify application # Run application // No memory errors ```
Related Issues
- [Fix Redis Connection Refused](/articles/fix-redis-connection-refused)
- [Fix Redis High Latency](/articles/fix-redis-high-latency)
- [Fix Redis Key Not Found](/articles/fix-redis-key-not-found)