SD System Design In Depth
0%
lesson system-designcachingexpiration

TTL expiration and cache reapers

Module 4.1: Caching Strategies & In-Memory State Updated Sep 17, 2026
📄 Cheat Sheet
The Evolution of Redis Key Expiration Algorithms
Redis The Evolution of Redis Key Expiration Algorithms Watch on YouTube ↗

A RedisConf talk from the Redis team on exactly this topic: how lazy and active expiration evolved, how the sampling loop works, and its CPU versus memory trade-offs.

0:00 / --:--

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 through active-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

ApproachBehaviourGood forRisk
Hard TTL onlyKey disappears and the next read missesSimple data, low trafficStampede on hot keys
Soft plus hard TTLAfter the soft TTL, serve the stale value and refresh asynchronously. After the hard TTL, the key is goneHot keys with tolerable stalenessNeeds a refresh-owner mechanism
Sliding TTLEach access extends the TTLSessionsKeys that are never idle never refresh their contents
No TTL plus explicit invalidationKey lives until it is deleted on writeData with reliable change eventsA 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.
Check yourself Question 1 of 3
Expired but resident recall
From: Mechanics

Millions of keys in a cache passed their TTL an hour ago, yet memory usage has barely dropped. Reads for those keys correctly return misses. What explains this?

Cache Stampede on a Hot Key

One hot key with a TTL; every client reads it about once per second. Rebuilding it takes one database query of fixed duration. All four strategies see the same request stream. With no protection, every request that arrives while the key is being rebuilt also misses and hits the database: roughly request rate × rebuild time queries per expiry.

Key Takeaways

Lazy expiry and bounded background cleanup avoid one timer per key.
Redis cleanup details vary by version and configuration.
Jitter and soft versus hard TTLs make load and staleness explicit.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue