Cache Eviction Policy Template
Template for documenting cache eviction rules per cache layer: LRU, LFU, TTL, FIFO, random eviction. Includes policy selection matrix, per-layer configuration, memory limits, and monitoring rules with code examples.
Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.
Overview
This template defines eviction policies for each cache layer in your infrastructure. Eviction policies determine which entries are removed when the cache reaches its memory limit. The wrong policy causes cache churn, low hit ratios, and degraded performance.
1. Eviction Policy Comparison
1.1 Policy Matrix
Policy | Evicts | Best for | Memory overhead | Complexity
────────┼───────────────────────┼───────────────────────┼─────────────────┼───────────
LRU | Least recently used | General purpose | Low (linked list)| Low
LFU | Least frequently used | Stable hot/cold sets | Medium (counters)| Medium
TTL | Expired entries | Time-sensitive data | None | None
FIFO | First inserted | Simple queues | Low | Low
Random | Random entry | Uniform access | None | None
TTL+LRU | Expired or least used | Mixed workloads | Low | Medium
1.2 Selection Guide
1. Does data have a natural expiration?
YES → TTL (with LRU as secondary)
NO → Continue.
2. Is access pattern uniform (no hot keys)?
YES → Random
NO → Continue.
3. Are hot keys stable (same keys always hot)?
YES → LFU
NO → Continue.
4. Default choice:
→ LRU (best general-purpose policy)
2. Per-Layer Configuration
2.1 Layer Overview
Layer | Technology | Eviction policy | Max memory
───────────────────┼─────────────────┼─────────────────┼──────────────
L1 (in-process) | LRU map | LRU | 256 MB
L2 (local Redis) | Redis | allkeys-lru | 4 GB
L3 (shared Redis) | Redis cluster | volatile-ttl | 32 GB
CDN | Cloudflare | LRU + TTL | Unlimited
Browser | HTTP cache | TTL | User-controlled
2.2 L1: In-Process Cache
from functools import lru_cache
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity: int):
self.cache = OrderedDict()
self.capacity = capacity
def get(self, key: str):
if key not in self.cache:
return None
self.cache.move_to_end(key)
return self.cache[key]
def put(self, key: str, value):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
self.cache.popitem(last=False) # Remove oldest (LRU)
# Usage
l1_cache = LRUCache(capacity=10000)
2.3 L2: Local Redis
# redis.conf — local Redis instance
maxmemory 4gb
maxmemory-policy allkeys-lru
maxmemory-samples 10
# Application-level configuration
import redis
local_redis = redis.Redis(
host='localhost',
port=6379,
db=0,
socket_timeout=2,
retry_on_timeout=True,
)
# Set with explicit TTL (secondary eviction)
local_redis.setex("user:123", 3600, user_data)
2.4 L3: Shared Redis Cluster
# redis-cluster.conf — shared Redis cluster
maxmemory 32gb
maxmemory-policy volatile-ttl
maxmemory-samples 20
# volatile-ttl evicts keys with the shortest TTL first
# Keys without TTL are never evicted (unless allkeys-* policy)
# Cluster client configuration
from redis.cluster import RedisCluster
cluster = RedisCluster(
startup_nodes=[{"host": "redis-1.internal", "port": 6379}],
max_connections=100,
retry_on_timeout=True,
socket_timeout=5,
)
# All keys MUST have TTL — volatile-ttl only evicts keys with TTL
cluster.setex("product:456", 7200, product_data)
3. Memory Limits
3.1 Memory Allocation Rules
Rule 1: Never allocate more than 75% of available RAM to cache
Rule 2: Reserve 15% for OS and background processes
Rule 3: Reserve 10% for connection buffers and overhead
Rule 4: Monitor actual usage — adjust if eviction rate is high
Rule 5: Set maxmemory to prevent OOM kills
3.2 Memory Calculation
Available server RAM: 16 GB
OS + processes: 2.4 GB (15%)
Connection overhead: 1.6 GB (10%)
Cache maxmemory: 12 GB (75%)
Per-key memory estimate:
String value (1KB): ~1.2 KB (including Redis overhead)
Hash (10 fields): ~2.5 KB
Set (100 members): ~4.5 KB
Estimated key capacity at 12 GB:
String keys: ~10 million
Hash keys: ~4.8 million
Set keys: ~2.7 million
3.3 Memory Monitoring
def check_cache_memory(r: redis.Redis) -> dict:
info = r.info()
used = info['used_memory']
max_mem = info.get('maxmemory', 0)
evicted = info.get('evicted_keys', 0)
hit_rate = info.get('keyspace_hits', 0) / max(
info.get('keyspace_hits', 0) + info.get('keyspace_misses', 0), 1
) * 100
return {
'used_memory_mb': used / 1024 / 1024,
'max_memory_mb': max_mem / 1024 / 1024,
'memory_usage_pct': (used / max_mem * 100) if max_mem else 0,
'evicted_keys': evicted,
'hit_rate_pct': hit_rate,
'policy': info.get('maxmemory_policy', 'unknown'),
}
4. Eviction Monitoring
4.1 Key Metrics
Metric | Alert threshold | Action
────────────────────────┼────────────────────────┼──────────────────────
evicted_keys | > 1000/min | Increase maxmemory
memory_usage_pct | > 90% | Scale up or reduce TTL
hit_rate | < 80% | Review eviction policy
miss_rate | > 20% | Check key expiration
connected_clients | > max_connections * 0.8| Increase connection pool
used_memory_dataset | Growing without bound | Check for keys without TTL
4.2 Monitoring Script
import redis
import time
def monitor_eviction(r: redis.Redis, interval: int = 60):
prev_evicted = 0
while True:
info = r.info()
current_evicted = info.get('evicted_keys', 0)
eviction_rate = (current_evicted - prev_evicted) / interval
used_mb = info['used_memory'] / 1024 / 1024
max_mb = info.get('maxmemory', 0) / 1024 / 1024
usage_pct = (used_mb / max_mb * 100) if max_mb else 0
print(f"[{time.strftime('%H:%M:%S')}] "
f"Memory: {used_mb:.0f}/{max_mb:.0f} MB ({usage_pct:.1f}%) | "
f"Evictions: {current_evicted} ({eviction_rate:.1f}/s) | "
f"Policy: {info.get('maxmemory_policy', 'n/a')}")
if eviction_rate > 100:
print("ALERT: High eviction rate — consider increasing maxmemory")
if usage_pct > 90:
print("ALERT: Memory usage above 90%")
prev_evicted = current_evicted
time.sleep(interval)
5. Eviction Policy Tuning
5.1 maxmemory-samples
Redis uses sampling to approximate eviction. Higher samples = more accurate but slower eviction.
# Default: 5 samples (fast, less accurate)
maxmemory-samples 5
# High-accuracy: 10 samples (slower eviction, better choices)
maxmemory-samples 10
# Maximum accuracy: 20 samples (use for large caches with hot keys)
maxmemory-samples 20
5.2 Tuning Checklist
- Monitor eviction rate for 24 hours after policy change
- Compare hit ratio before and after policy change
- Verify no keys without TTL when using volatile-* policy
- Check memory fragmentation ratio (info: mem_fragmentation_ratio)
- Adjust maxmemory-samples if eviction choices are poor
- Document policy choice and reasoning per cache layer
6. Common Issues
6.1 Keys Without TTL Under volatile-* Policy
# Problem: volatile-ttl only evicts keys with TTL.
# Keys without TTL accumulate and cause OOM.
# Fix: Find and fix keys without TTL
def find_keys_without_ttl(r: redis.Redis, pattern: str = "*") -> list:
no_ttl_keys = []
for key in r.scan_iter(match=pattern, count=1000):
if r.ttl(key) == -1: # -1 means no expiry
no_ttl_keys.append(key)
return no_ttl_keys
# Fix: Set TTL on all keys
for key in find_keys_without_ttl(r):
r.expire(key, 3600) # Default 1 hour TTL
6.2 Cache Churn (High Eviction, Low Hit Rate)
When the cache is too small for the working set, entries are evicted before they are reused.
Diagnosis:
- evicted_keys > 5000/min
- hit_rate < 50%
- memory_usage > 95%
Solutions:
1. Increase maxmemory (scale up)
2. Reduce TTL (let entries expire naturally)
3. Switch to LFU (better for stable hot keys)
4. Reduce key set (cache only hot keys)
5. Add another cache layer (L1 in-process)
FAQ
When should I use volatile-ttl vs allkeys-lru?
Use volatile-ttl when all keys have TTL and you want to evict the ones closest to expiration first. This is ideal for time-sensitive data where expired data is less valuable. Use allkeys-lru when some keys may not have TTL and you want to evict the least recently used regardless of TTL. allkeys-lru is the safer default because it handles keys without TTL gracefully.
How do I choose between LRU and LFU?
Use LRU when access patterns change over time (what was hot yesterday may not be hot today). LRU adapts quickly to changing patterns. Use LFU when the hot key set is stable (the same keys are always hot). LFU tracks access frequency, so a key accessed 1000 times stays in cache longer than one accessed 10 times, even if the 10-access key was used more recently. Most applications benefit from LRU. Switch to LFU only if you observe that LRU is evicting frequently-accessed keys.
What is memory fragmentation and how do I fix it?
Memory fragmentation occurs when Redis allocates memory blocks but cannot reuse freed blocks efficiently. Check mem_fragmentation_ratio in Redis INFO. A ratio above 1.5 means 50% of allocated memory is fragmented. Fix by running MEMORY PURGE (Redis 4.0+) or restarting Redis during a maintenance window. Use activedefrag yes in Redis 6.0+ for automatic defragmentation.
Should I use different eviction policies for different Redis databases?
Yes. Use different Redis databases (db 0, db 1, etc.) or separate instances for different data types with different eviction needs. For example: db 0 for user sessions (volatile-ttl, 15 min TTL), db 1 for product catalog (allkeys-lru, 1 hour TTL), db 2 for rate limiting (volatile-ttl, 60 sec TTL). This prevents one data type from evicting another when memory is constrained.
How do I estimate the right cache size?
Measure your working set — the keys that are accessed within a typical time window. Start with a cache size that fits 120% of the working set. Monitor eviction rate and hit ratio for one week. If hit ratio is below 90% and evictions are high, increase the size. If hit ratio is above 95% and memory usage is below 60%, decrease the size to save costs. Use Redis MEMORY USAGE on sampled keys to estimate average key size.
See Also
Related Resources
Cache Strategy Decision Template
Decision template for choosing cache strategies per use case: no-cache, cache-aside, read-through, write-through, write-back, and refresh-ahead. Includes decision matrix, TTL guidelines, invalidation rules, and code examples.
DocCache Warmup Runbook
Runbook for warming caches after deployment, restart, or incident: identify hot keys, preload strategies, progressive warmup, health checks, and rollback procedures with code examples and automation scripts.
DocCDN Cache Rules Template
Template for defining CDN caching rules and edge behavior: cache keys, TTL by content type, query parameter handling, header forwarding, purge strategies, and origin shield configuration with code examples.