Expiration and Memory Management
Introduction
Redis stores everything in memory, which gives it speed but also makes memory a finite and precious resource. This chapter explains two mechanisms that keep your dataset under control: expiration lets individual keys disappear automatically after a set time, and eviction removes keys when total memory usage crosses a configured limit. Together they ensure that Redis stays fast and does not crash from an out-of-memory error.
Prerequisites
- A running Redis server and comfort with
redis-cli - Familiarity with the basic data types (String, Hash, List, Set, Sorted Set)
- Access to edit the Redis configuration file (
redis.conf)
Key Expiration
Redis allows you to attach a time-to-live (TTL) to any key. When the TTL reaches zero, the key is deleted automatically. This is the foundation of caching: store computed data, let it expire, and regenerate it on demand.
Setting and Checking Expiration
A TTL return value of -1 means the key exists but has no expiration. A return of -2 means the key does not exist at all.
Expiration Precision
# Set expiration in milliseconds
PEXPIRE temp:lock 5000
# Set expiration at an absolute Unix timestamp (seconds)
EXPIREAT session:user:42 1735689600
# Set expiration at an absolute Unix timestamp (milliseconds)
PEXPIREAT temp:flag 1735689600000Tip
Cache Pattern
When caching database query results, set the expiration slightly longer than your typical regeneration time. If generating a product page takes 200 ms and your peak traffic is 1000 requests per second, a 60-second TTL prevents 59,999 unnecessary database hits.
How Redis Deletes Expired Keys
Redis uses two complementary strategies:
- Lazy expiration: When a client accesses a key, Redis checks if it expired and deletes it on the spot. An expired key that nobody reads still consumes memory temporarily.
- Active expiration: Redis runs a background task that samples keys with expiration and deletes those that have passed their TTL. The algorithm runs more frequently as the ratio of expired keys in the sample increases.
Because of lazy expiration, you may notice that MEMORY USAGE briefly includes keys whose TTL already hit zero. They disappear as soon as they are accessed or sampled.
Warning
Expiration Is Not a Real-Time Guarantee
Do not build business logic that assumes a key vanishes at the exact millisecond its TTL reaches zero. Use Redis expiration for cache management, not for sub-second precision timing.
Expiration on Data Structures
You set expiration on the key, not on individual elements inside a data structure.
# The entire Hash expires after 60 seconds
EXPIRE user:42:profile 60
# There is no native way to expire only the "email" field inside that HashIf you need per-field expiration, common workarounds include storing each field as a separate top-level key or using a secondary index that tracks TTLs in a Sorted Set.
Memory Eviction Policies
Even with expiration, memory can fill up if keys accumulate faster than they disappear. Redis provides maxmemory and maxmemory-policy to define what happens when the memory ceiling is reached.
Configuring maxmemory
# redis.conf
maxmemory 256mb
maxmemory-policy allkeys-lruYou can also change these at runtime:
redis-cli CONFIG SET maxmemory 268435456
redis-cli CONFIG SET maxmemory-policy allkeys-lrumaxmemory 0 (the default on 64-bit systems) means no limit. On 32-bit systems, the implicit limit is 3 GB. Production deployments should always set an explicit maxmemory.
Choosing an Eviction Policy
| Policy | What It Evicts | When to Use |
|---|---|---|
noeviction | Nothing; returns errors on write | Development, or when you never want silent data loss |
allkeys-lru | Least recently used keys, any key | General caching; most common production choice |
volatile-lru | Least recently used keys with an expiration | When some keys must stay forever (no TTL) |
allkeys-lfu | Least frequently used keys, any key | When access patterns have strong hot/cold splits |
volatile-lfu | Least frequently used keys with an expiration | Mixed dataset where permanent keys must survive |
allkeys-random | Random keys, any key | Rarely useful; for uniform workloads only |
volatile-random | Random keys with an expiration | When you do not care which cached item dies |
volatile-ttl | Keys with shortest remaining TTL | When you want to keep long-lived cache entries |
Tip
Start with allkeys-lru
If you are unsure, choose allkeys-lru. It removes the coldest data while keeping hot data in memory, which matches the behavior of most caches.
LRU vs LFU
- LRU (Least Recently Used) tracks when a key was last accessed. A key touched 10 minutes ago is evicted before a key touched 10 seconds ago.
- LFU (Least Frequently Used) tracks how often a key is accessed. A key hit 10,000 times in the last hour survives over a key hit once yesterday, even if the once-yesterday key was accessed more recently.
LFU is better when your dataset has long-tail access patterns: a small group of keys is extremely hot, and the rest is rarely touched. LRU is simpler and works well for most applications.
Monitoring Memory
# Overview of memory usage
redis-cli INFO memory
# Key sections to watch
# used_memory: total bytes allocated by Redis
# used_memory_rss: bytes seen by the operating system
# maxmemory: your configured limit
# maxmemory_policy: current eviction strategyWhen used_memory approaches maxmemory, evictions begin. Watch the evicted_keys counter in INFO stats to see how aggressively Redis is reclaiming space.
The Large Key Problem
A "large key" is any key whose value consumes disproportionate memory or causes latency spikes. A Hash with one million fields, a List with ten million elements, or a single String holding 100 MB of JSON are all large keys.
Detecting Large Keys
# Scan for the biggest keys (can be slow on production)
redis-cli --bigkeys
# Memory usage of a specific key in bytes
redis-cli MEMORY USAGE user:42:history
# Breakdown of a Hash's internal encoding
redis-cli DEBUG OBJECT user:42:historyMitigation Strategies
| Symptom | Solution |
|---|---|
| Hash with millions of fields | Shard into user:42:history:0, user:42:history:1 by ID range or time bucket |
| List growing without bound | Use LTRIM after every LPUSH, or switch to a Stream with MAXLEN |
| Single String holding huge JSON | Split into a Hash, or store in a document database and cache only IDs |
| Sorted Set with old irrelevant scores | Use ZREMRANGEBYSCORE or ZREMRANGEBYRANK in a cron job |
Warning
Deletion Latency
Deleting a very large key blocks the single event loop. On a busy server, a DEL on a 100 MB key can freeze all other clients for milliseconds or longer. Use UNLINK instead, which deletes the key asynchronously in a background thread.
# Non-blocking delete
UNLINK huge:listFAQ
Does eviction remove keys that have not expired yet?
Yes. Policies starting with allkeys- evict any key regardless of TTL. Policies starting with volatile- only evict keys that have a TTL set. If no volatile keys remain and the policy is volatile-*, Redis falls back to returning errors.
Can I set a default expiration for all new keys?
No. Redis has no global default TTL. Application frameworks and Redis wrappers sometimes add this behavior client-side. Otherwise, you must explicitly call EXPIRE or use SETEX in your code.
What happens if I set EXPIRE on a key that already has one?
The old expiration is overwritten. The key now uses the new TTL.
Is there a performance cost to having many keys with expiration?
A small one. Redis maintains an expiration dictionary. With millions of expiring keys, the active expiration process uses slightly more CPU. For most workloads, this is negligible compared to the convenience.
Why is used_memory_rss larger than used_memory?
used_memory is what Redis logically holds. used_memory_rss is what the operating system observes, including memory fragmentation and allocator overhead. A large gap suggests memory fragmentation, which can sometimes be improved by restarting Redis after enabling activedefrag (available in Redis 4+).
Should I use UNLINK for every delete?
For small keys, DEL and UNLINK are equally fast. Reserve UNLINK for keys you suspect are large, or make it your default habit if your application deletes keys of unpredictable size.