Redis Eviction and Expiry

intermediate redis eviction lru lfu ttl memory

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

Decision Guide
Pure cache (all data is expendable)
→ Use allkeys-lru or allkeys-lfu
Mix of cache + permanent data
→ Use volatile-lru (set TTL only on cache keys)
Some keys are way more popular than others
→ Use allkeys-lfu (keeps hot keys around)
Data loss is unacceptable
→ 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:

  1. Randomly samples 20 keys from the set of keys with TTL
  2. Deletes any that are expired
  3. 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:

Eviction
Triggered by: memory limit reached
What's removed: depends on policy
Purpose: make room for new data
Expiration
Triggered by: TTL timer runs out
What's removed: only that specific key
Purpose: auto-cleanup of stale data

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.