LFU (Least Frequently Used) and LRU (Least Recently Used) are two common cache eviction policies in Redis, determining which items get kicked out when the cache is full.

Imagine you have a Redis cache storing user session data.

# Simulate adding 1000 keys, each accessed multiple times
for i in {1..1000}; do
  redis-cli SET session:$i "user_data_for_$i" EX 3600
  for j in {1..5}; do # Access each key 5 times
    redis-cli GET session:$i
  done
done

# Now, let's add more keys to trigger eviction
for i in {1001..1500}; do
  redis-cli SET session:$i "user_data_for_$i" EX 3600
done

If you’re using LRU, the keys that haven’t been accessed recently will be evicted. In the above example, session:1 through session:1000 were all accessed 5 times, but session:1001 through session:1500 were only accessed once (when they were set). LRU would likely evict the older, less recently accessed keys first, potentially removing session:1 even though it was accessed multiple times.

LFU, on the other hand, evicts the keys that have been accessed least frequently. So, in our example, session:1 through session:1000 would be considered "hotter" than session:1001 through session:1500. LFU would try to keep session:1 in the cache because it’s been hit 5 times, while session:1001 (only hit once) would be a prime candidate for eviction.

Redis implements LFU by tracking access counts for each key. When a key is accessed, its count is incremented. If the count exceeds a certain threshold, it’s periodically halved to prevent overflow and give recently accessed, but less frequent, items a chance. The maxmemory-policy configuration directive controls this. Setting it to allkeys-lfu or volatile-lfu enables LFU eviction for all keys or just those with an expiry set, respectively.

LRU is the default in Redis. It works by maintaining a linked list of keys, moving accessed keys to the head of the list. When eviction is needed, keys are removed from the tail. This is a simpler mechanism, requiring less overhead per access than LFU’s counting.

LFU shines when you have a few "power user" items that are accessed far more often than others, and you want to prioritize keeping those in cache. Think of a popular product page on an e-commerce site versus a less-visited product. LFU will aggressively keep the popular page in memory. LRU, while good at general caching, might evict that popular page if it hasn’t been hit in the last few minutes, only to be immediately re-fetched and re-added.

LRU is generally a good all-rounder, especially when access patterns are more uniform or when you don’t have clear "hot" items. It’s also more memory-efficient in terms of the per-key overhead. The maxmemory-policy can be set to allkeys-lru or volatile-lru for LRU eviction.

Here’s how you’d configure it in your redis.conf:

# For LFU eviction
maxmemory-policy allkeys-lfu

# For LRU eviction (default, but explicit is good)
maxmemory-policy allkeys-lru

The actual counter for LFU is a logarithmic counter, not a simple linear one. This means that the counter’s value grows slower than the actual number of accesses, and it’s also subject to decay. This prevents a single key from completely dominating the counter and ensures that recently accessed keys, even if they haven’t reached a very high count, have a chance to be retained.

The choice between LFU and LRU often comes down to understanding your application’s access patterns and what you’re trying to optimize for: guaranteed retention of consistently hot items (LFU) versus general recency-based caching (LRU).

Understanding the decay factor in LFU’s counter is key to tuning its behavior for your specific workload.

Want structured learning?

Take the full Redis course →