# 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. 1.Wrong eviction policy - Using inappropriate policy for use case
  2. 2.Insufficient memory - maxmemory set too low
  3. 3.No TTL on keys - Keys not expiring naturally
  4. 4.Key access patterns - Hot keys being evicted
  5. 5.Memory fragmentation - Effective memory lower than configured
  6. 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 CasePolicyCommand
Pure cacheallkeys-lruEvict least recently used
Cache + persistent datavolatile-lruEvict LRU among keys with TTL
Hot dataallkeys-lfuEvict least frequently used
Time-sensitive cachevolatile-ttlEvict keys with shortest TTL
Random cacheallkeys-randomEvict random keys
No evictionnoevictionReturn error on OOM
bash
# Set policy
redis-cli CONFIG SET maxmemory-policy allkeys-lru

Solution 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 maxmemory limit
  • [ ] Choose correct maxmemory-policy for 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
  • [Redis Out of Memory](./fix-redis-out-of-memory)
  • [Redis Slow Commands](./fix-redis-slow-commands)