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

bash
# 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 1735689600000

Tip

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:

  1. 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.
  2. 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.

bash
# 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 Hash

If 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

conf
# redis.conf
maxmemory 256mb
maxmemory-policy allkeys-lru

You can also change these at runtime:

bash
redis-cli CONFIG SET maxmemory 268435456
redis-cli CONFIG SET maxmemory-policy allkeys-lru

maxmemory 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

PolicyWhat It EvictsWhen to Use
noevictionNothing; returns errors on writeDevelopment, or when you never want silent data loss
allkeys-lruLeast recently used keys, any keyGeneral caching; most common production choice
volatile-lruLeast recently used keys with an expirationWhen some keys must stay forever (no TTL)
allkeys-lfuLeast frequently used keys, any keyWhen access patterns have strong hot/cold splits
volatile-lfuLeast frequently used keys with an expirationMixed dataset where permanent keys must survive
allkeys-randomRandom keys, any keyRarely useful; for uniform workloads only
volatile-randomRandom keys with an expirationWhen you do not care which cached item dies
volatile-ttlKeys with shortest remaining TTLWhen 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

bash
# 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 strategy

When 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

bash
# 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:history

Mitigation Strategies

SymptomSolution
Hash with millions of fieldsShard into user:42:history:0, user:42:history:1 by ID range or time bucket
List growing without boundUse LTRIM after every LPUSH, or switch to a Stream with MAXLEN
Single String holding huge JSONSplit into a Hash, or store in a document database and cache only IDs
Sorted Set with old irrelevant scoresUse 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.

bash
# Non-blocking delete
UNLINK huge:list

FAQ

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.