Redis Expiration Strategies and Memory Eviction Mechanisms
This article explains how Redis removes expired keys using periodic and lazy deletion, describes the slave expiration handling, details the asynchronous memory reclamation commands like UNLINK and FLUSHALL ASYNC, and outlines the various maxmemory eviction policies including LRU, LFU, and their implementations.
In everyday development, Redis keys are often set with an expiration time, but understanding how Redis deletes expired keys and its memory eviction mechanisms is essential because Redis is single‑threaded and deletions could cause blocking.
Periodic Deletion Strategy
Redis stores each key with an expiration in a separate dictionary and, by default, scans it every 100 ms:
Randomly select 20 keys.
Delete the keys among those that have expired.
If more than a quarter of the sampled keys are expired, repeat the process.
Scanning all keys would block the single‑threaded server, so each scan is limited to a default maximum of 25 ms. When many keys expire simultaneously, Redis may loop multiple times until the expired‑key ratio falls below 1/4, which can cause latency spikes and even cache avalanches under high concurrency.
Slave Expiration Strategy
Slaves do not perform active expiration scans. When a key expires on the master, a DEL command is written to the AOF and propagated to slaves, which delete the key upon receiving the command. Because propagation is asynchronous, a delay can lead to temporary inconsistencies between master and slave.
Lazy Deletion Strategy
While the DEL command frees memory instantly for most keys, deleting a very large object (e.g., a hash with millions of fields) or executing FLUSHDB/FLUSHALL on a large dataset can block the single thread. Redis 4.0 introduced the lazy‑free mechanism to offload such deletions to background threads.
UNLINK Command
The UNLINK command performs a lazy deletion by handing the memory reclamation to a background thread:
> unlink key
OKFLUSHDB / FLUSHALL Async
Adding the ASYNC option to FLUSHDB or FLUSHALL makes the operation asynchronous:
> flushall async
OKAsynchronous Queue
After the main thread removes a key’s reference, it packages the memory‑free operation as a task and pushes it onto an asynchronous queue processed by a background thread. Small keys may still be freed immediately, while larger ones are reclaimed later.
Memory Eviction Mechanisms
When Redis’s memory usage exceeds the maxmemory limit, one of several eviction policies (configured via maxmemory-policy ) is applied:
noeviction : writes fail, reads and deletes continue.
allkeys-lru : evicts the least‑recently‑used key among all keys (recommended for cache usage).
allkeys-random : evicts a random key among all keys.
volatile-lru : evicts the least‑recently‑used key among keys with an expiration.
volatile-random : evicts a random key among keys with an expiration.
volatile-ttl : evicts the key with the shortest remaining TTL among keys with an expiration.
LRU Algorithm
Redis implements an approximate LRU using a 24‑bit field in each object header to store the last access time. A simple Python OrderedDict example demonstrates how a true LRU could be built:
from collections import OrderedDict
class LRUDict(OrderedDict):
def __init__(self, capacity):
self.capacity = capacity
self.items = OrderedDict()
def __setitem__(self, key, value):
old_value = self.items.get(key)
if old_value is not None:
self.items.pop(key)
self.items[key] = value
elif len(self.items) < self.capacity:
self.items[key] = value
else:
self.items.popitem(last=True)
self.items[key] = value
def __getitem__(self, key):
value = self.items.get(key)
if value is not None:
self.items.pop(key)
self.items[key] = value
return value
def __repr__(self):
return repr(self.items)
d = LRUDict(10)
for i in range(15):
d[i] = i
print dApproximate LRU in Redis
Redis does not use a strict LRU; instead it samples a few keys (default 5) and evicts the oldest among the sample. The number of sampled keys is controlled by maxmemory_samples . Sampling is performed on all keys for allkeys-* policies or only on keys with an expiration for volatile-* policies.
LFU Policy
Redis 4.0 added the LFU (Least Frequently Used) eviction mode, which tracks access frequency using a logarithmic counter stored in the same 24‑bit field. The field is split into an 8‑bit logarithmic counter ( logc ) and a 16‑bit last‑decrement‑time ( ldt ), allowing frequency decay over time.
Redis Object Header
typedef struct redisObject {
unsigned type:4; // object type (string, list, set, etc.)
unsigned encoding:4; // internal encoding (raw, ziplist, etc.)
unsigned lru:24; // LRU or LFU information
int refcount; // reference count
void *ptr; // pointer to the actual data
} robj;In LRU mode, the lru field stores the server’s 24‑bit clock (Unix timestamp modulo 2²⁴). Accessing a key updates this field. In LFU mode, the same field stores logc (frequency) and ldt (last decrement time), enabling more precise eviction based on recent access patterns.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.