Concrete scenario
Millions of sessions expire near midnight, but deleting every key with an individual timer would overload the cache.
Mechanics
Caches commonly combine lazy expiration on access with bounded background sampling or scanning. Exact Redis sample counts, time budgets, and frequency depend on version and configuration, so monitor expired-but-resident memory and event-loop latency rather than relying on one fixed algorithm.
Failure mode
Synchronized TTLs can create a miss storm, lazy cleanup can retain memory, and different nodes can expire copies at different moments. Clock steps and replication behavior require product-specific testing.
Trade-off
Aggressive reaping frees memory sooner but steals CPU from requests. Jittered TTLs smooth load; soft and hard expiry separate freshness from the maximum staleness the product permits.
Worked example: jittering a session TTL
A login service writes 2 million sessions between 09:00 and 09:05 each morning with a fixed 12-hour TTL. At 21:00 they all expire within five minutes, which is about 6,700 expirations per second on top of normal traffic. Any user still active triggers a reload from the session store.
Add uniform jitter of plus or minus 30 minutes and the same 2 million expirations spread over about 65 minutes, which is roughly 510 per second. Better still, use a sliding TTL for active users, refreshing EXPIRE on access, so only idle sessions expire.
How Redis actually expires keys
- Lazy: every read checks the key's expiry timestamp and deletes it if it has passed.
- Active: a periodic cycle, driven by
hz(default 10 times per second), samples keys from the set of keys that have a TTL. If a large fraction of the sample has expired, it repeats, bounded by a CPU time budget. Redis 6 and later make the effort tunable throughactive-expire-effort. - Replicas do not expire keys independently. The primary sends explicit DEL commands, although replicas hide logically expired keys from reads.
Soft TTL versus hard TTL
| Approach | Behaviour | Good for | Risk |
|---|---|---|---|
| Hard TTL only | Key disappears and the next read misses | Simple data, low traffic | Stampede on hot keys |
| Soft plus hard TTL | After the soft TTL, serve the stale value and refresh asynchronously. After the hard TTL, the key is gone | Hot keys with tolerable staleness | Needs a refresh-owner mechanism |
| Sliding TTL | Each access extends the TTL | Sessions | Keys that are never idle never refresh their contents |
| No TTL plus explicit invalidation | Key lives until it is deleted on write | Data with reliable change events | A missed invalidation means stale data forever |
Other systems
- Memcached expires lazily and also runs an LRU crawler that reclaims expired items in the background.
- Cassandra TTLs turn cells into tombstones that are only purged at compaction after
gc_grace_seconds, so TTL-heavy tables can accumulate tombstones and slow reads. - DynamoDB TTL deletes expired items in the background, typically within a few days, so queries must still filter on the expiry attribute.