Redis stores everything in memory. Memory is finite. So what happens when Redis runs out of it?
This is controlled by two things: the maxmemory setting (how much memory Redis is allowed to use) and the eviction policy (what Redis does when it hits that limit). Getting these right is critical for production.
Setting the Memory Limit
# In redis.conf
maxmemory 2gb
# Or set at runtime
CONFIG SET maxmemory 2gb
# Check current memory usage
INFO memory
If we don’t set maxmemory, Redis on a 64-bit system will use as much memory as it wants — which can be dangerous. On 32-bit systems, there’s an implicit 3 GB limit.
The 8 Eviction Policies
When Redis hits the memory limit and we try to write new data, it looks at the eviction policy to decide what to do.
noeviction
Redis refuses to accept new writes and returns an error. Reads still work fine. This is the default.
Use it when: we’d rather fail loudly than silently lose cached data.
allkeys-lru
Evict the least recently used key across ALL keys in the database.
Use it when: we’re using Redis as a general-purpose cache and want old/cold data to make room for new data.
volatile-lru
Same as allkeys-lru, but only considers keys that have a TTL set. Keys without a TTL are never evicted.
Use it when: we have a mix of permanent data (no TTL) and cache data (with TTL), and we only want to evict the cache.
allkeys-lfu
Evict the least frequently used key across all keys. LFU tracks how often a key is accessed, not just when.
Use it when: we want to keep “popular” keys around even if they haven’t been accessed in the last few seconds.
volatile-lfu
LFU, but only among keys with a TTL.
allkeys-random
Pick a random key and evict it. No tracking, no overhead.
Use it when: all keys are equally important and we just need to make space.
volatile-random
Random eviction, but only among keys with a TTL.
volatile-ttl
Evict the key with the shortest remaining TTL — the one closest to expiring anyway.
Use it when: keys with shorter TTLs are less important.
Choosing the Right Policy
→ Use
allkeys-lru or allkeys-lfu
→ Use
volatile-lru (set TTL only on cache keys)
→ Use
allkeys-lfu (keeps hot keys around)
→ Use
noeviction (and handle the errors in our app)
# Set eviction policy
CONFIG SET maxmemory-policy allkeys-lru
# In redis.conf
maxmemory-policy allkeys-lru
LRU in Redis Is an Approximation
Here’s an important detail: Redis does NOT implement true LRU. A real LRU would require tracking the access time of every single key and maintaining a linked list — that’s expensive in terms of memory.
Instead, Redis uses sampled LRU. When it needs to evict, it picks a random sample of keys (default: 5) and evicts the least recently used one from that sample. It’s not perfect, but it’s surprisingly close to true LRU with much less overhead.
# Increase the sample size for better accuracy (at the cost of CPU)
maxmemory-samples 10 # default is 5
With a sample size of 10, Redis’s approximation is nearly indistinguishable from true LRU. At 5, it’s still quite good for most workloads.
LFU works similarly — it’s also sampled, not exact.
TTL and Key Expiration
Besides eviction (which happens when memory is full), Redis also supports TTL-based expiration — we can set a timer on any key, and Redis will automatically delete it when the timer runs out.
Setting TTL
# Set TTL in seconds
SET session:abc "user_42"
EXPIRE session:abc 3600 # expires in 1 hour
# Set TTL in milliseconds
PEXPIRE session:abc 3600000 # same thing, in ms
# Set TTL at a specific Unix timestamp
EXPIREAT session:abc 1743465600 # expires at this exact time
# Set key with TTL in one command
SETEX session:abc 3600 "user_42"
# Check remaining TTL
TTL session:abc # returns seconds remaining (-1 = no TTL, -2 = key doesn't exist)
PTTL session:abc # returns milliseconds remaining
# Remove TTL (make key permanent again)
PERSIST session:abc
How Expiration Actually Works
Redis uses two strategies to clean up expired keys:
1. Lazy expiration (passive) When we try to access a key, Redis checks if it’s expired. If it is, Redis deletes it right there and returns nothing. This means expired keys can sit in memory until someone asks for them.
2. Active expiration (periodic scan) Redis runs a background task ~10 times per second that:
- Randomly samples 20 keys from the set of keys with TTL
- Deletes any that are expired
- If more than 25% of the sampled keys were expired, repeat immediately
This combination ensures that expired keys don’t pile up too much, while keeping the overhead low.
In simple language, Redis is lazy about expiration — it doesn’t watch a clock for every key. Instead, it checks when we access a key (lazy) and periodically sweeps through a sample of keys (active). This is why at any given moment, there might be expired keys still sitting in memory that haven’t been cleaned up yet.
Eviction vs Expiration
These are two different things and it’s easy to mix them up:
A key can be evicted even if its TTL hasn’t expired (if memory is full). And a key can expire even if there’s plenty of memory left.
Practical Configuration
Here’s a solid starting point for a production Redis cache:
# redis.conf
maxmemory 4gb # set based on available RAM
maxmemory-policy allkeys-lru # good default for a cache
maxmemory-samples 5 # default, increase to 10 for better accuracy
# Lazy-free eviction (non-blocking deletion for large keys)
lazyfree-lazy-eviction yes # delete evicted keys in background thread
lazyfree-lazy-expire yes # delete expired keys in background thread
The lazyfree settings are important for large keys. Without them, deleting a key with millions of elements blocks the main Redis thread. With lazyfree enabled, the deletion happens in a background thread.