Redis Memory Growth, Eviction Policies & Large Key Governance: A Complete Operational Guide
This comprehensive guide analyzes Redis memory growth causes, details all eight eviction policies with their trade-offs, and provides battle-tested techniques for discovering and safely removing large keys without blocking the instance, backed by a real-world case study of a 1.3GB Hash with 1.2M fields.
1. Problem Background: A Typical "Slow Memory Rise"
An 8-instance product detail cache cluster, each with maxmemory 16gb and maxmemory-policy allkeys-lru, ran stably for six months. Then alerts fired: three instances saw used_memory creep from 9GB to 15.5GB, used_memory_rss neared 17GB, and the host began swapping. The on-call fix was raising maxmemory to 18GB — a painkiller. Two weeks later the same issue returned. Investigation revealed three facts: used_memory vs used_memory_rss gap was 1.3GB, but mem_fragmentation_ratio stayed at 1.08 — not fragmentation. INFO stats showed evicted_keys growing only a few hundred per day — eviction barely triggered because memory was expanded before hitting maxmemory. --bigkeys scan found a Hash with 1.2M fields and a List with 900K elements; MEMORY USAGE confirmed the Hash consumed 1.3GB.
Root cause: the business wrote a "per-SKU accumulated behavior log" into a single Hash via HSET sku:behavior with no TTL. The "memory growth" was fundamentally a data-model design flaw; maxmemory is merely the last safety net.
2. Core Knowledge: Where Memory Goes
2.1 Three-Layer Memory View
INFO memorykey fields: used_memory: Redis's own accounting via zmalloc — data structures, object overhead, replication buffers, client buffers. used_memory_rss: OS resident set size = used_memory + allocator fragmentation (allocated but unreleased pages) + code segment + stacks. used_memory_dataset = used_memory - used_memory_overhead; pure data payload. Overhead includes main dict, expires dict, client output buffers.
Interpretation rules:
rss/used_memory 1.0–1.1 : Normal — no action.
Ratio 1.2–1.5 : Acceptable fragmentation — watch trend, avoid frequent add/delete.
Ratio > 1.5 : Severe fragmentation, usually from mass deletes — enable activedefrag.
rss < used_memory : Swapping occurred — Emergency — check swap & memory overcommit.
used_memory_peak far above current : Historical spike — investigate past large keys or bulk writes. rss < used_memory means the OS swapped Redis pages to disk — a single swap can push P99 from 1ms to 500ms. Common in containers where the container memory limit is set below Redis maxmemory or the host overcommits memory. Verify with free -m and cgroup memory.max / memory.current. Rule of thumb: set maxmemory to 70–75% of memory.max to leave room for fragmentation, replication buffers, and fork COW.
2.2 Data Structure Memory Overhead
Each type has a "small-object encoding" and a "large-object encoding"; crossing the threshold jumps memory usage. Critical for diagnosing "why 1M keys take 200MB".
String : small encoding int / embstr (<= 44 bytes for embstr); large encoding raw (SDS).
List : small encoding listpack ( list-max-listpack-size 128, element <= 64 bytes); large encoding quicklist (multi-node).
Hash : small encoding listpack ( hash-max-listpack-entries 128, hash-max-listpack-value 64); large encoding hashtable.
Set : small encoding intset / listpack ( set-max-intset-entries 512, set-max-listpack-entries 128); large encoding hashtable.
ZSet : small encoding listpack ( zset-max-listpack-entries 128, zset-max-listpack-value 64); large encoding skiplist + dict.
Example: Hash with <=128 fields uses listpack (~20–30 bytes/field). Beyond 128 fields or any value >64 bytes, it converts to hashtable — each entry stores key pointer, value pointer, and dictEntry; jemalloc size-class alignment pushes per-field overhead to 80–100 bytes. The same data can triple in memory after crossing the threshold. Hence "stuffing 10K user states into one Hash" may cost more than 10K separate Strings once fields exceed 128. Check encoding with OBJECT ENCODING key.
2.3 SDS & Object Header Overhead
String memory = SDS header (length-dependent) + data +
