Redis Expiration and Eviction: TTL, Lazy Deletion and maxmemory Policies
· tech
📑 Contents
- Expiration: a key’s time is up; how does it get cleared
- Eviction: memory is full; who gets kicked out
- redis-cli: TTL and maxmemory operations
- Reflections
- Expiration vs eviction: separate “leaving on its own” from “no room, asked to leave”
- Approximate LRU: another “trade a little precision for memory”
- The wrong eviction policy is a hidden bomb: “fine normally, explodes when full”
Use Redis as a cache and sooner or later you hit two questions: how does a key with a TTL get cleared once it expires? and what happens when memory is full? The two are often lumped together, but they’re different things — the former is expiration: “this key’s own time is up, it should go”; the latter is eviction: “there’s no room left, someone has to be asked to leave”. Separating the two is the foundation of using Redis well as a cache.
Expiration: a key’s time is up; how does it get cleared
You’d think that the moment a key’s TTL is reached, Redis deletes it and frees the memory? It doesn’t. Redis uses two mechanisms together to clear expired keys, and neither guarantees “immediately”:
The counter-intuitive point here is worth remembering: a key’s TTL being reached doesn’t mean it vanishes from memory at once. If nobody accesses it (lazy deletion isn’t triggered) and the background sampling hasn’t picked it yet, it lies in memory for a while — a GET returns nil (logically expired), but the physical memory hasn’t been freed. Incidentally, a replica doesn’t delete expired keys on its own; it waits for the DEL the master sends after expiring the key — to keep master and replica consistent, the master is the authority on expiration.
Eviction: memory is full; who gets kicked out
Expiration is a key leaving on its own schedule; eviction is something else — when memory usage hits the maxmemory ceiling, Redis has to actively kick out some keys to make room. Which ones? The eviction policy decides, and there are 8 of them (plus one “don’t kick anyone”):
The key to choosing a policy is one question: is this Redis a pure cache, or does it also hold things that can’t be lost?
- Pure cache (every key can be rebuilt from the database behind it): use
allkeys-lruorallkeys-lfu, let Redis freely kick out the least useful and keep the memory for hot data. - Mixed with important data: use
volatile-*— evict only “disposable data with a TTL set”, protecting the important keys without a TTL; or usenoevictionand manage capacity yourself. - Never leave the default
noevictionin a “used as a cache” scenario — the moment memory fills, every write starts failing, while you may think Redis is just “clearing old data automatically”.
redis-cli: TTL and maxmemory operations
Expiration and eviction each have a set of commands, matching “leaving on its own” and “no room, kick whom”:
# expiration (TTL)
SET session:1 "..." EX 3600 # set the value with a 1-hour TTL in one go (most common)
EXPIRE user:1 60; TTL user:1 # add a TTL afterwards / check seconds left (-1 = permanent, -2 = doesn't exist)
PERSIST user:1 # remove the TTL, back to permanent
# eviction (memory ceiling and policy)
CONFIG SET maxmemory 2gb
CONFIG SET maxmemory-policy allkeys-lru # one of the 8 policies (see the matrix above)
INFO stats # check evicted_keys: non-zero and climbing = scale up or change policy
OBJECT FREQ hotkey # under an LFU policy, see a key's access frequency
Watch two numbers and you have the essentials: TTL tells you how long a key has left to live, and evicted_keys climbing in INFO stats is the first alarm that “memory hit the ceiling and eviction has started” — the watershed between adding memory and changing the eviction policy.
Reflections
Expiration vs eviction: separate “leaving on its own” from “no room, asked to leave”
When I started with Redis I treated expiration and eviction as the same thing, and got the maxmemory configuration completely backwards. Then it clicked: expiration is a key’s personal schedule (TTL reached, it should go); eviction is the system’s space pressure (memory full, someone has to be asked to leave) — independent of each other; a key without a TTL never “expires” but can still be “evicted”. The distinction looks like a detail, but it directly decides your design: which keys get a TTL (to expire naturally), what the memory ceiling is, whom to evict on hitting it. Think of “time’s up” and “no room” as two separate pressures, and a lot of Redis capacity questions become clear.
Approximate LRU: another “trade a little precision for memory”
Redis’s LRU isn’t “evict exactly the least recently used one” — maintaining a full LRU list costs too much memory. It uses sampled approximation instead: pick a few keys at random, evict the least recently used among them. It’s Redis’s consistent philosophy again, the same thinking as HyperLogLog estimating hundreds of millions in 12KB and fork COW copying only modified pages — between “fully exact” and “save resources”, cleverly choose less exact. And it’s usually good enough: cache eviction never needed “mathematically optimal”; approximate LRU’s effect on hit rate is negligible while it saves the heavy cost of maintaining an exact structure. I increasingly think that telling “where exactness is needed from where approximate is fine” is one of the clearest gaps between senior engineers and beginners.
The wrong eviction policy is a hidden bomb: “fine normally, explodes when full”
The eviction policy is the kind of setting that “bites you with its default if you don’t actively think about it”. The default noeviction is a disaster for a “used as a cache” scenario — calm while memory isn’t full, and then once data grows to the ceiling, rather than clearing old data automatically, every write starts failing, and the whole service goes down with it. I’ve seen this incident more than once: everyone assumed Redis would “clear the old stuff itself” when in fact it was refusing the new. It taught me a more general lesson: for any resource with a “ceiling”, actively think through “what happens when we hit it” — automatic reclamation, refusing service, or outright crash? That “boundary behaviour” is invisible day to day, yet it’s usually the star of the 3am incident. Thinking through every ceiling’s hit-the-top behaviour before launch is a very worthwhile paranoia.