Cache Eviction Policies in Redis: LRU, LFU, and More Explained
Learn how Redis cache eviction policies like LRU, LFU, and volatile-ttl work, and how to choose the right one for your Python application to keep your cache efficient under memory pressure.
Advertisement
When you're working with Redis as a cache, you'll eventually hit a wall—the memory limit. Redis is an in-memory data store, which means it keeps everything in RAM. And RAM, as you probably know, isn't infinite. So what happens when you set a maxmemory limit and Redis runs out of space? That's where eviction policies come in.
Think of it like a busy kitchen with limited counter space. When the counter gets full, you have to decide what to throw away to make room for new ingredients. Redis does the same thing with your cached data. The policy you choose determines which keys get the boot.
Why Eviction Policies Matter
If you're running a production application with PythonSkillset, you've probably set up Redis as a cache to speed up database queries or session storage. Without an eviction policy, Redis would just throw an error when memory fills up. That's rarely what you want. A well-chosen eviction policy keeps your cache useful even under memory pressure.
Let's walk through the most common eviction policies Redis offers, and when you'd want to use each one.
The Default: No Eviction
By default, Redis uses noeviction. This means when memory is full, any write operation that would increase memory usage returns an error. Reads still work fine. This is safe but not practical for most caching scenarios. You'd only want this if you're using Redis as a database where data loss is unacceptable.
LRU: Least Recently Used
LRU is the most popular eviction policy. It works on a simple idea: the keys you haven't touched in the longest time are the ones most likely to be evicted first.
Redis offers two flavors:
- allkeys-lru: Evicts any key, regardless of whether it has an expiration set.
- volatile-lru: Evicts only keys that have an expiration time (TTL) set.
LRU is great for general-purpose caching. Imagine you're running a PythonSkillset blog where you cache article previews. Older articles that nobody reads anymore will naturally get evicted first, making room for fresh content. It's a solid default choice.
But here's the catch: Redis doesn't implement a perfect LRU. That would require tracking every access timestamp, which uses too much memory. Instead, Redis uses an approximation. It samples a small pool of keys (by default 5) and evicts the oldest among them. This works well in practice, but it's not perfect.
LFU: Least Frequently Used
LFU takes a different approach. Instead of looking at when a key was last used, it looks at how often it's been used. The idea is that frequently accessed keys are more valuable than rarely accessed ones.
Redis offers two LFU variants:
- allkeys-lfu: Evicts any key based on frequency.
- volatile-lfu: Evicts only keys with TTLs.
LFU is particularly useful when you have a mix of hot and cold data. For example, on PythonSkillset, you might have a few popular articles that get thousands of views daily, while most articles get only occasional visits. LFU would keep those popular articles in cache while evicting the less popular ones.
But LFU has a quirk: it can suffer from "frequency aging." A key that was popular a week ago but is now dead might still have a high frequency count. Redis handles this with a decay mechanism that gradually reduces frequency over time, so old popularity doesn't lock out new content.
The Full List of Eviction Policies
Redis actually gives you eight options. Here's the complete lineup:
- noeviction: Returns errors on write operations when memory is full.
- allkeys-lru: Evicts the least recently used key from the entire keyspace.
- allkeys-lfu: Evicts the least frequently used key from the entire keyspace.
- volatile-lru: Evicts the least recently used key among those with an expiration set.
- volatile-lfu: Evicts the least frequently used key among those with an expiration set.
- allkeys-random: Evicts a random key from the entire keyspace.
- volatile-random: Evicts a random key among those with an expiration set.
- volatile-ttl: Evicts the key with the shortest remaining time to live.
When to Use Each Policy
Let's break this down with real scenarios you might face at PythonSkillset.
allkeys-lru for General Caching
This is the go-to for most caching use cases. If you're caching API responses, database query results, or rendered HTML snippets, LRU works beautifully. The assumption is that if a key hasn't been accessed recently, it's probably not needed anymore.
For example, if you cache user profile data, a user who logs in daily will keep their profile in cache. A user who hasn't visited in months will naturally get evicted. Simple and effective.
allkeys-lfu for Hot Data
LFU shines when you have a clear "hot" set of data that gets accessed far more than everything else. Think of a news website where the top story gets 90% of traffic. LRU might evict that top story if it hasn't been accessed in a few minutes during a lull, but LFU would keep it because of its high access count.
At PythonSkillset, we've seen LFU work well for caching API responses where certain endpoints are called thousands of times more than others. The frequency counter ensures that popular data stays cached even if it hasn't been accessed in the last few seconds.
volatile-ttl: The Time-Based Approach
This one is straightforward: Redis evicts keys with the shortest remaining time to live. If you set TTLs on your cache keys, this policy will clear out the ones that are about to expire anyway.
This is useful when you have a mix of short-lived and long-lived cache entries. For example, session tokens might have a 30-minute TTL, while product catalog data might have a 24-hour TTL. With volatile-ttl, Redis will evict the session tokens first, which makes sense because they're about to expire anyway.
Random Eviction: The Underdog
Random eviction sounds lazy, but it's surprisingly effective in some cases. Redis offers both allkeys-random and volatile-random. The idea is simple: when memory is full, pick a random key and evict it.
This works well when all your cached data has roughly equal value. If you're caching things like user avatars or static assets where every item is equally important, random eviction is fast and fair. It doesn't have the overhead of tracking access times or frequencies.
volatile-ttl: The Time-Sensitive Option
This policy looks at keys with expiration times and evicts the one with the shortest remaining TTL. It's useful when you have a clear hierarchy of expiration times. For example, you might cache session data with a 15-minute TTL and product details with a 1-hour TTL. Redis will evict the session data first, which makes sense because it was going to expire soon anyway.
But there's a catch: if you have many keys with the same TTL, Redis picks one randomly among them. It's not a perfect "shortest first" algorithm, but it's good enough for most cases.
How to Choose the Right Policy
There's no one-size-fits-all answer. Here's a practical guide based on what you're caching:
- General API responses: Use
allkeys-lru. It's simple and works well for most cases. - Content with clear popularity differences: Use
allkeys-lfu. This keeps your most popular content cached. - Session data with TTLs: Use
volatile-ttl. Sessions expire naturally, so evict the ones closest to expiration. - When you don't care which keys get evicted: Use
allkeys-random. It's the cheapest in terms of CPU. - When you want to be safe: Use
noevictionand handle errors in your application code.
Setting the Policy in Redis
You can set the eviction policy in your Redis configuration file or at runtime. Here's how you'd do it in your PythonSkillset application using the redis-py library:
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
# Set maxmemory to 100MB
r.config_set('maxmemory', '100mb')
# Set eviction policy to allkeys-lru
r.config_set('maxmemory-policy', 'allkeys-lru')
You can also set these in your redis.conf file:
maxmemory 100mb
maxmemory-policy allkeys-lru
The Sampling Trade-off
Here's something most tutorials don't tell you: Redis doesn't scan the entire keyspace to find the best key to evict. That would be too slow. Instead, it samples a small number of keys and picks the best candidate from that sample.
The default sample size is 5 keys. You can change this with the maxmemory-samples configuration. A larger sample size gives you a more accurate eviction decision but uses more CPU. A smaller sample is faster but less precise.
For most applications, the default of 5 is fine. But if you have millions of keys and you're seeing suboptimal evictions, you might bump it up to 10 or 20. Just remember that each eviction now checks more keys, which adds latency.
Real-World Example: PythonSkillset's Caching Strategy
Let me give you a concrete example from PythonSkillset. We cache article content to reduce database load. Most articles get a few hundred views, but our top 10 articles get tens of thousands. We also cache user session data with a 30-minute TTL.
For the article cache, we use allkeys-lfu. The popular articles stay cached because they're accessed frequently. The long-tail articles get evicted when memory runs low, which is fine because they're rarely requested anyway.
For session data, we use volatile-ttl. Sessions have a natural expiration, so evicting the ones closest to expiry makes sense. If a user comes back after their session expired, they'll just get a new one.
The Performance Impact
Here's something you might not expect: eviction policies have different CPU costs. Random eviction is the cheapest because Redis just picks a key at random. LRU and LFU require sampling, which costs a tiny bit more CPU. For most applications, this difference is negligible. But if you're running Redis at massive scale with millions of keys and constant evictions, it can add up.
The maxmemory-samples setting controls how many keys Redis samples before evicting. The default is 5. If you increase it to 10 or 20, you get better eviction decisions but slightly higher CPU usage. For most PythonSkillset users, the default is fine.
Setting the Policy in Your PythonSkillset Application
Here's how you'd configure eviction in your Python code:
import redis
cache = redis.Redis(host='localhost', port=6379, decode_responses=True)
# Set max memory to 500MB
cache.config_set('maxmemory', '500mb')
# Use allkeys-lfu for frequently accessed data
cache.config_set('maxmemory-policy', 'allkeys-lfu')
You can also check what's currently set:
print(cache.config_get('maxmemory-policy'))
# Output: {'maxmemory-policy': 'allkeys-lfu'}
Which One Should You Choose?
Here's a quick decision guide:
- You're building a general-purpose cache: Start with
allkeys-lru. It's the most forgiving and works well for most workloads. - You have clear hot data: Use
allkeys-lfu. This keeps your most popular content cached. - You rely heavily on TTLs: Use
volatile-ttl. It complements your expiration strategy. - You don't care which keys get evicted: Use
allkeys-random. It's the simplest and fastest. - You want to be safe: Use
noevictionand handle the error in your code. This gives you full control.
A Practical Example
Let's say you're building a PythonSkillset feature that caches user search results. Each search query returns a list of articles. You set a TTL of 5 minutes because search results change frequently.
You'd probably use volatile-ttl here. When memory gets full, Redis evicts the search results that are closest to expiring anyway. This keeps your cache fresh and avoids evicting data that still has a long life ahead.
Here's how you'd configure it:
import redis
cache = redis.Redis(host='localhost', port=6379, decode_responses=True)
cache.config_set('maxmemory', '200mb')
cache.config_set('maxmemory-policy', 'volatile-ttl')
What About Performance?
All eviction policies have some CPU cost, but it's usually negligible. The exception is when you're constantly hitting the memory limit and evicting keys on every write. In that case, you might want to increase your maxmemory or optimize your cache usage.
Random eviction is the cheapest. LRU and LFU are slightly more expensive because they need to sample keys. But in practice, even on busy Redis instances, the overhead is tiny.
A Word of Caution
Don't set your maxmemory too low. If Redis is constantly evicting keys, you're spending more time on eviction than on serving data. Monitor your cache hit rate. If it drops below 80%, consider increasing your memory limit or optimizing what you cache.
Also, remember that eviction policies only kick in when memory is full. If you have plenty of RAM, no eviction happens at all. So the first step is always to set a reasonable maxmemory that gives your cache room to breathe.
Final Thoughts
Choosing the right eviction policy is about understanding your data access patterns. LRU is the safe bet for most applications. LFU is better when you have clear popularity differences. volatile-ttl works well with expiration-based caching. And random eviction is surprisingly effective when you don't have strong patterns.
At PythonSkillset, we've found that starting with allkeys-lru and monitoring cache hit rates is a good approach. If you see that your most popular data is getting evicted too often, switch to allkeys-lfu. If you're using TTLs heavily, try volatile-ttl.
The key is to monitor and adjust. Redis gives you the tools—you just need to pick the right one for your workload.
Advertisement
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.