Redis Key Expiration
How TTLs are enforced, why expired keys can persist in memory, and the operations that silently remove an expiry.
A TTL marks a key for automatic deletion. Expiry is how a cache stays bounded without an eviction policy having to do the work.
SET shop:session:abc "…" EX 3600 # set with expiry, atomically
EXPIRE shop:cart:9f3a 1800 # add expiry to an existing key
TTL shop:cart:9f3a # seconds remaining, -1 no TTL, -2 no key
PERSIST shop:cart:9f3a # remove the expiryHow expiry actually happens
Two mechanisms, and neither is instantaneous:
- Lazy expiration. When a key is accessed, Redis checks whether it has expired and deletes it then.
- Active expiration. A background cycle samples keys with TTLs and removes expired ones, repeating while it finds a high proportion expired.
The consequence: an expired key that nobody accesses continues to occupy memory until the active
cycle happens to sample it. Memory does not drop the instant a large batch of keys expires. On
replicas, expired keys are not deleted independently — the primary sends an explicit DEL, so a
replica may briefly hold keys that are logically gone.
Operations that remove the TTL
Note that TTLs apply to the whole key. There is no per-field expiry in a hash in core Redis — a common wrong assumption when modelling objects as hashes.
Choosing TTLs
- Always set one on cache entries. A cache without expiry is a memory leak with good latency.
- Add jitter. Thousands of keys created together with an identical TTL expire together, causing a synchronised miss storm against the origin. See Cache Stampede.
ttl = base_ttl + random.randint(0, base_ttl // 10)- Match the TTL to tolerable staleness, not to convenience. If a stale value for ten minutes is acceptable, ten minutes is the answer.
Monitoring
INFO stats # expired_keys, evicted_keys
INFO keyspace # keys and keys-with-expiry per databasedb0:keys=1450000,expires=1200000 tells you 250,000 keys have no TTL. If that number grows, keys
are being created without expiry or having it cleared — worth investigating before memory does it
for you.
Keyspace notifications can publish expiry events, which is occasionally useful:
notify-keyspace-events Ex