Open-Source Wikis

/

Redis

/

Systems

/

Memory management

redis/redis

Memory management

Active contributors: antirez, Oran Agra, debing.sun, zhaozhao.zz.

Purpose

Redis is an in-memory database. Memory management is the most performance-sensitive subsystem. Five concerns:

  1. Allocation — accounting wrappers around the underlying allocator (jemalloc by default).
  2. Eviction — reclaiming memory when maxmemory is exceeded.
  3. Expiration — proactively removing keys past their TTL.
  4. Defragmentation — actively moving allocations around to fight allocator fragmentation.
  5. Lazy free — pushing big-object frees into a background thread.

Source layout

File Role
src/zmalloc.c, src/zmalloc.h Allocator wrappers and accounting.
src/object.c, src/object.h The robj value wrapper, refcounting, encodings, shared objects, MEMORY USAGE.
src/evict.c LRU/LFU/random eviction.
src/expire.c TTL bookkeeping and active expiration cron.
src/lazyfree.c Asynchronous freeing via the BIO_LAZY_FREE thread.
src/defrag.c Active defragmentation by relocating allocations.
src/estore.c, src/estore.h Per-DB expiration store.
src/ebuckets.c, src/ebuckets.h Bucketed time-ordered structure used by estore.
src/keymeta.c, src/keymeta.h Per-key metadata (LRU bits, hash-field TTLs, …).
src/childinfo.c Pipe-based reporting from forked children (BGSAVE/BGREWRITEAOF) for accurate memory accounting.
src/memory_prefetch.c Software prefetching to hide L1/L2 misses on hot dict lookups.
src/memtest.c Memory testing routine invoked by the crash handler and --test-memory.

zmalloc

zmalloc wraps the underlying allocator and bumps a global counter so INFO memory is accurate without scanning. It exposes:

void *zmalloc(size_t size);
void *zcalloc(size_t size);
void *zrealloc(void *ptr, size_t size);
char *zstrdup(const char *s);
void zfree(void *ptr);
size_t zmalloc_used_memory(void);
size_t zmalloc_get_rss(void);
size_t zmalloc_get_allocator_info(...);

When built with jemalloc (the default on Linux), zmalloc calls je_* directly and gets per-arena stats and active-defragmentation hooks. Without jemalloc, the wrappers fall back to libc with allocation-size accounting via malloc_usable_size (Linux) or a leading-size prefix.

The robj value wrapper

Every value in the keyspace is a robj (src/object.h):

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS;
    int refcount;
    void *ptr;
} robj;

Six types: OBJ_STRING, OBJ_LIST, OBJ_SET, OBJ_ZSET, OBJ_HASH, OBJ_STREAM. Encodings tell which underlying primitive holds the data — OBJ_ENCODING_RAW, EMBSTR, INT, HT, LISTPACK, LISTPACK_EX, QUICKLIST, INTSET, SKIPLIST, STREAM, LISTPACK_HFE. The encoding can change at runtime when the value grows past a threshold.

Refcount-based sharing exists for read-only objects: small integer strings (0..9999) live in server.shared.integers[], and a small set of common error replies are pre-allocated.

Eviction

maxmemory caps the user-visible memory used by the dataset (not the RSS — replication buffers, AOF buffers, and Lua state are excluded by default; see maxmemory-clients).

When a write would push usage past the cap, performEvictions (in src/evict.c) is called. It selects victims according to maxmemory-policy:

Policy Meaning
noeviction Reply with -OOM instead of evicting.
allkeys-lru Approximated LRU across all keys.
allkeys-lfu Approximated LFU across all keys.
allkeys-random Random across all keys.
volatile-lru LRU among keys with a TTL.
volatile-lfu LFU among keys with a TTL.
volatile-ttl Pick the key with the shortest remaining TTL.
volatile-random Random among keys with a TTL.

The LRU/LFU implementations are approximated — Redis samples maxmemory-samples keys (default 5) and evicts the worst. Higher sample counts give better accuracy at higher cost. The 24-bit lru field on robj packs either an LRU clock or an 8+16 LFU counter+access-time pair depending on maxmemory-policy.

Expiration

TTLs live in a per-DB expiration store (src/estore.c). The store is backed by an ebuckets structure (src/ebuckets.c) — a bucketed, time-ordered radix that lets active expiration sample and evict efficiently.

Two cleanup paths:

  • Lazy expiration — when a command accesses a key, expireIfNeeded checks the TTL and deletes if expired before continuing.
  • Active expirationactiveExpireCycle (called from the cron) walks the ebuckets, sampling keys, deleting expired ones. The cycle adapts to spend roughly active-expire-effort % of the cron's CPU budget.

For hash-field TTLs (Redis 7.4+), the same model applies but per-field. src/keymeta.c and src/entry.c carry the per-field timestamps; the t_hash.c code paths invoke field-level expiration.

PERSIST <key> removes a TTL. EXPIRE/PEXPIRE/EXPIREAT/PEXPIREAT set one (with NX/XX/GT/LT modifiers). OBJECT IDLETIME/OBJECT FREQ expose the lru/lfu fields.

Lazy free

Big objects (a hash with millions of fields, a sorted set with millions of members) take noticeable time to free synchronously. lazyfree-lazy-eviction, lazyfree-lazy-expire, lazyfree-lazy-server-del, lazyfree-lazy-user-del, and lazyfree-lazy-user-flush redirect those frees to the BIO_LAZY_FREE thread. The implementation is src/lazyfree.c:

int dbAsyncDelete(redisDb *db, robj *key);
void freeObjAsync(robj *key, robj *obj, int dbid);
size_t lazyfreeGetPendingObjectsCount(void);
size_t lazyfreeGetFreedObjectsCount(void);

The BIO thread is src/bio.c's BIO_LAZY_FREE. Jobs are submitted with bioCreateLazyFreeJob and processed asynchronously.

UNLINK is the explicit user-visible lazy-delete; semantically equivalent to DEL but never blocks.

Active defragmentation

When jemalloc fragments memory (heap utilisation drops because frequently freed allocations leave gaps), src/defrag.c proactively moves objects to denser arenas. The cron checks the fragmentation ratio (mem_frag_ratio); if above active-defrag-threshold-lower it starts a defrag pass; if above active-defrag-threshold-upper it ramps up effort.

The defragger walks the keyspace and for each object asks jemalloc whether the allocation is in a "stale" arena; if so, it reallocs into a new spot and updates pointers. Strings, listpacks, dicts, quicklists, intsets, and rax all have type-specific defrag callbacks.

Module data types must implement defrag if they want defrag support; otherwise their objects are skipped.

Memory inspection

Command Purpose
INFO memory Top-line memory accounting.
MEMORY USAGE <key> Byte cost of a specific key (object + key SDS + dict entry overhead + per-element overhead).
MEMORY STATS Detailed breakdown including allocator-internal stats.
MEMORY DOCTOR Heuristic recommendations for memory tuning.
MEMORY MALLOC-STATS Dumps jemalloc's mallctl stats.
MEMORY PURGE Hint to jemalloc to release physical pages.
OBJECT ENCODING <key> The encoding of a specific value (helps verify that a small list is a listpack, etc).

Where to start modifying

  • Add an eviction policy — extend the MAXMEMORY_* enum in src/evict.c and the policy table.
  • Tune cron samplingactiveExpireCycle accepts a type argument (ACTIVE_EXPIRE_CYCLE_FAST or _SLOW); the budget split is in the cron.
  • Add a MEMORY subcommand — extend memoryCommand in src/object.c.
  • Improve defrag for a custom type — implement the type's defrag callback in its t_*.c.
  • robj — the value wrapper.
  • ebuckets — TTL storage.
  • PersistenceSHUTDOWN [SAVE] calls into eviction-aware paths.
  • Modules — module data types provide their own free/defrag callbacks.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Memory management – Redis wiki | Factory