Databases 13 min read

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.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Redis Expiration Strategies and Memory Eviction Mechanisms

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
OK

FLUSHDB / FLUSHALL Async

Adding the ASYNC option to FLUSHDB or FLUSHALL makes the operation asynchronous:

> flushall async
OK

Asynchronous 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 d

Approximate 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.

cacheMemory ManagementRedisLRULFUexpirationLazy Free
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.