Expiration Strategy - TTL Design
Redis keys can have a TTL (time to live). When the TTL expires, the key is deleted. Setting TTLs well avoids unbounded memory growth, limits staleness in caches, and reduces the risk of cache avalanche when many keys expire at once. This article covers how expiration works, how to choose TTLs, and how to add jitter.
Overview
- EXPIRE / PEXPIRE: Set seconds or milliseconds until the key is removed. SET with
EX/PXsets the value and TTL in one command. - Expiration semantics: Redis deletes expired keys lazily (on access) and in a background cycle; so memory may not drop exactly at TTL. A key is considered expired when accessed and is not returned.
- TTL choice: Long TTL for stable data (fewer DB hits, more staleness risk). Short TTL for volatile or “absent” placeholders. Always set a TTL for cache keys so cold or bad data does not stay forever.
- Jitter: Add random offset to TTL so keys do not all expire at the same time (avoids thundering herd and cache avalanche).
Example
Example 1: Setting TTL on set
RedisSET user:1001 "..." EX 3600 SET session:abc "..." PX 900000 EXPIRE cache:item:1 7200 TTL user:1001
EX 3600= 3600 seconds;PX 900000= 900000 ms.TTLreturns remaining seconds (-1 = no TTL, -2 = key does not exist).
Example 2: TTL by use case
| Use case | Suggested TTL | Reason |
|---|---|---|
| User profile cache | 5–60 min | Balance freshness vs DB load |
| Session | 15–30 min | Security and memory |
| “Absent” placeholder | 1–5 min | Avoid penetration; short so real data can appear soon |
| Rate limit window | 1–60 s | Reset window; match limit granularity |
| Static reference data | 1–24 h | Rarely changes; long TTL OK with invalidation on update |
Example 3: Jitter to avoid avalanche
Javaint baseTtl = 3600; int jitter = ThreadLocalRandom.current().nextInt(0, 600); redis.setex(key, baseTtl + jitter, value); // 3600–4200 s
- Keys loaded in the same batch will expire over a 10-minute window instead of at the same second, spreading load and avoiding cache stampede when they all expire.
Core Mechanism / Behavior
- Lazy deletion: When a key is read, Redis checks expiry and deletes it if expired before returning. So expired keys can still occupy memory until touched or cleaned.
- Active expiry: Redis runs an internal job that samples keys and deletes expired ones. With many keys and few accesses, memory can stay high until the sampler hits them.
- Replication: Replicas do not expire keys independently; they wait for the primary’s DEL or the primary’s expiry propagation so behaviour is consistent.
Key Rules
- Always set a TTL on cache keys (and “absent” placeholders) to avoid unbounded growth and to bound staleness.
- Add jitter (e.g. base TTL + random 0–10%) when setting many keys at once so they do not all expire together.
- Prefer EXPIRE/SET EX in application code for cache-aside; use a consistent policy (e.g. same base TTL and jitter range per key type) for predictable behaviour.
What's Next
See Cache-Aside Pattern and Caching Pitfalls for cache usage and avalanche. See Data Types for key design. See Redis Persistence for how RDB/AOF interact with expiry (expired keys are not written).