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:
- Allocation — accounting wrappers around the underlying allocator (jemalloc by default).
- Eviction — reclaiming memory when
maxmemoryis exceeded. - Expiration — proactively removing keys past their TTL.
- Defragmentation — actively moving allocations around to fight allocator fragmentation.
- 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,
expireIfNeededchecks the TTL and deletes if expired before continuing. - Active expiration —
activeExpireCycle(called from the cron) walks the ebuckets, sampling keys, deleting expired ones. The cycle adapts to spend roughlyactive-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 insrc/evict.cand the policy table. - Tune cron sampling —
activeExpireCycleaccepts atypeargument (ACTIVE_EXPIRE_CYCLE_FASTor_SLOW); the budget split is in the cron. - Add a
MEMORYsubcommand — extendmemoryCommandinsrc/object.c. - Improve defrag for a custom type — implement the type's
defragcallback in itst_*.c.
Related pages
- robj — the value wrapper.
- ebuckets — TTL storage.
- Persistence —
SHUTDOWN [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.