# Redis Key Eviction Issues
Symptoms
- Keys disappearing unexpectedly
- Cache hit rate dropping suddenly
- Important data being evicted
- Error messages about maxmemory
- Application errors due to missing keys
Root Causes
- 1.Wrong eviction policy - Using inappropriate policy for use case
- 2.Insufficient memory - maxmemory set too low
- 3.No TTL on keys - Keys not expiring naturally
- 4.Key access patterns - Hot keys being evicted
- 5.Memory fragmentation - Effective memory lower than configured
- 6.Uneven key distribution - Some keys growing too large
Diagnosis Steps
Step 1: Check Eviction Policy and Memory
```bash # Check current eviction policy redis-cli CONFIG GET maxmemory-policy
# Check memory usage redis-cli INFO memory | grep -E "used_memory|maxmemory|evicted"
# Check eviction statistics redis-cli INFO stats | grep evicted ```
Step 2: Analyze Key Expiration
```bash # Find keys without expiration redis-cli --scan | while read key; do ttl=$(redis-cli TTL "$key") if [ "$ttl" -eq -1 ]; then echo "$key" fi done | head -100
# Count keys without TTL redis-cli --scan | while read key; do redis-cli TTL "$key" done | grep -c -1 ```
Step 3: Analyze Key Access Patterns
```bash # Use MEMORY DOCTOR redis-cli MEMORY DOCTOR
# Check object idle time (LRU/LFU) redis-cli OBJECT IDLETIME "mykey" redis-cli OBJECT FREQ "mykey" # For LFU policy
# Find large keys redis-cli --bigkeys ```
Step 4: Monitor Evictions Over Time
```bash # Get eviction count redis-cli INFO stats | grep evicted_keys
# Monitor in real-time watch -n 1 'redis-cli INFO stats | grep evicted_keys' ```
Solutions
Solution 1: Choose Appropriate Eviction Policy
```bash # View current policy redis-cli CONFIG GET maxmemory-policy
# Set eviction policy based on use case ```
Policy Selection Guide:
| Use Case | Policy | Command |
|---|---|---|
| Pure cache | allkeys-lru | Evict least recently used |
| Cache + persistent data | volatile-lru | Evict LRU among keys with TTL |
| Hot data | allkeys-lfu | Evict least frequently used |
| Time-sensitive cache | volatile-ttl | Evict keys with shortest TTL |
| Random cache | allkeys-random | Evict random keys |
| No eviction | noeviction | Return error on OOM |
# Set policy
redis-cli CONFIG SET maxmemory-policy allkeys-lruSolution 2: Set TTL on All Cache Keys
```bash # Set TTL when creating keys redis-cli SET "cache:user:123" "data" EX 3600 # 1 hour
# Or with SETEX redis-cli SETEX "cache:user:123" 3600 "data"
# Add TTL to existing keys redis-cli EXPIRE "cache:user:123" 3600
# Batch set TTL on pattern redis-cli --eval set_ttl.lua , "cache:*" 3600 ```
Lua script for batch TTL:
```lua -- set_ttl.lua local pattern = ARGV[1] local ttl = tonumber(ARGV[2]) local cursor = '0' local count = 0
repeat local reply = redis.call('SCAN', cursor, 'MATCH', pattern, 'COUNT', 1000) cursor = reply[1] local keys = reply[2] for i = 1, #keys do redis.call('EXPIRE', keys[i], ttl) count = count + 1 end until cursor == '0'
return count ```
Solution 3: Increase Memory Limit
```bash # Check current limit redis-cli CONFIG GET maxmemory
# Increase limit redis-cli CONFIG SET maxmemory 8gb
# For permanent change, edit config # /etc/redis/redis.conf maxmemory 8gb ```
Solution 4: Protect Important Keys
```bash # Make keys non-volatile (no TTL, won't be evicted by volatile policies) redis-cli SET "important:config" "data" # Don't set TTL
# Or use separate database for persistent data redis-cli -n 0 SET "cache:data" "temporary" EX 3600 redis-cli -n 1 SET "config:data" "permanent" ```
Solution 5: Optimize LFU Settings
For LFU eviction policy:
```bash # Adjust LFU decay time (default 1 minute) redis-cli CONFIG SET lfu-decay-time 10 # 10 minutes
# Adjust LFU log factor (counter precision) redis-cli CONFIG SET lfu-log-factor 10 ```
Solution 6: Implement Cache Warming
```bash # Preload important keys # Warm cache script
#!/bin/bash # warm_cache.sh
# Load frequently accessed keys redis-cli SET "cache:popular:1" "data" EX 3600 redis-cli SET "cache:popular:2" "data" EX 3600
# Load on application startup # Ensure hot data is always in cache ```
Solution 7: Monitor and Alert on Evictions
```bash #!/bin/bash # eviction_monitor.sh
THRESHOLD=1000
EVICTED=$(redis-cli INFO stats | grep evicted_keys | cut -d: -f2 | tr -d '\r')
if [ "$EVICTED" -gt "$THRESHOLD" ]; then echo "WARNING: High eviction count: $EVICTED" # Get memory info redis-cli INFO memory | grep -E "used_memory|maxmemory" fi ```
Solution 8: Use Key Tags for Priority
```bash # High priority keys (no TTL, protected) redis-cli SET "config:important" "data"
# Medium priority (long TTL) redis-cli SET "cache:important" "data" EX 86400 # 24 hours
# Low priority (short TTL) redis-cli SET "cache:temporary" "data" EX 300 # 5 minutes ```
Application-Level Solutions
Node.js Example
```javascript const Redis = require('ioredis'); const redis = new Redis();
// Always set TTL for cache keys async function setCache(key, value, ttl = 3600) { await redis.set(key, JSON.stringify(value), 'EX', ttl); }
// Handle cache misses gracefully async function getCache(key) { const value = await redis.get(key); if (value === null) { // Key was evicted, fetch from source return await fetchFromDatabase(key); } return JSON.parse(value); }
// Use prefixes for different priorities const CACHE_PREFIXES = { HOT: 'cache:hot:', // Long TTL WARM: 'cache:warm:', // Medium TTL COLD: 'cache:cold:' // Short TTL };
async function setWithPriority(key, value, priority = 'WARM') { const prefix = CACHE_PREFIXES[priority]; const ttl = { HOT: 86400, WARM: 3600, COLD: 300 }[priority]; await redis.set(prefix + key, JSON.stringify(value), 'EX', ttl); } ```
Python Example
```python import redis import json
r = redis.Redis()
def cache_get(key, fetch_func, ttl=3600): """Get from cache, fetch on miss, set TTL""" value = r.get(key) if value is None: # Cache miss - fetch from source value = fetch_func() r.set(key, json.dumps(value), ex=ttl) else: value = json.loads(value) return value
# Use different TTLs based on data importance def cache_with_priority(key, data, priority='medium'): priorities = { 'high': 86400, # 24 hours 'medium': 3600, # 1 hour 'low': 300 # 5 minutes } ttl = priorities.get(priority, 3600) r.set(key, json.dumps(data), ex=ttl) ```
Configuration Best Practices
```ini # /etc/redis/redis.conf
# Set appropriate memory limit (leave room for OS) maxmemory 4gb
# Choose policy based on use case maxmemory-policy allkeys-lru
# For LRU policy, sample more keys for better eviction decisions maxmemory-samples 10
# LFU settings (if using LFU policy) lfu-decay-time 1 lfu-log-factor 10
# Timeout for idle connections timeout 300 ```
Monitoring Eviction Metrics
```bash # Key metrics to track redis-cli INFO stats | grep -E "evicted_keys|keyspace_hits|keyspace_misses"
# Calculate hit rate HITS=$(redis-cli INFO stats | grep keyspace_hits | cut -d: -f2 | tr -d '\r') MISSES=$(redis-cli INFO stats | grep keyspace_misses | cut -d: -f2 | tr -d '\r') HIT_RATE=$(echo "scale=4; $HITS / ($HITS + $MISSES) * 100" | bc) echo "Cache hit rate: $HIT_RATE%" ```
Prevention Checklist
- [ ] Set appropriate
maxmemorylimit - [ ] Choose correct
maxmemory-policyfor use case - [ ] Set TTL on all cache keys
- [ ] Monitor eviction count
- [ ] Track cache hit rate
- [ ] Use separate databases for cache vs persistent data
- [ ] Implement graceful cache miss handling
- [ ] Warm cache on application startup
Related Errors
- [Redis Out of Memory](./fix-redis-out-of-memory)
- [Redis Slow Commands](./fix-redis-slow-commands)