Server

Redis as a Cache: maxmemory and Eviction Policies

By Prabath Thalangama· October 24, 2026· 3 min read
#redis#caching#performance

Introduction

Used as a cache, Redis needs a memory ceiling and a rule for what to drop when it hits it. Out of the box it has no limit and noeviction — it'll consume all RAM and then start refusing writes.

Set the ceiling

# redis.conf
maxmemory 4gb
maxmemory-policy allkeys-lfu

maxmemory should leave room for replication buffers, client buffers, and the fork COW spike if persistence is on — budget ~25% headroom below physical RAM.

The eight policies

Policy Evicts Use when
noeviction nothing — writes error with OOM Redis is a datastore, not a cache
allkeys-lru least-recently-used, any key general cache, recency matters
allkeys-lfu least-frequently-used, any key general cache — usually best
allkeys-random random key uniform access, LRU overhead not worth it
volatile-lru LRU among keys with a TTL mixed: some keys are permanent
volatile-lfu LFU among keys with a TTL same, frequency-based
volatile-random random key with a TTL same, cheap
volatile-ttl key with the shortest remaining TTL you've encoded priority as TTL

LFU vs LRU: LRU evicts the key not touched recently, so a burst of one-off scans can flush your hot set. LFU tracks a frequency counter that decays over time, so a rarely-but-steadily-used key survives a scan storm. For most caches, allkeys-lfu is the better default (available since Redis 4).

volatile-* policies fail like noeviction if no keys have a TTL — a common "why is Redis rejecting writes" surprise. Use allkeys-* unless you genuinely have a permanent subset.

Tuning LFU

lfu-log-factor 10        # higher = counter saturates slower (distinguishes very hot keys)
lfu-decay-time 1         # minutes; how fast the counter decays when idle

Defaults are fine to start. OBJECT FREQ key (requires an LFU policy) shows a key's counter; OBJECT IDLETIME key (LRU) shows seconds since last access.

How expiry works

  • Setting EX/PX/EXPIRE stores an expiry timestamp.
  • Lazy expiry: a key is checked on access; if expired, it's deleted then and a miss is returned.
  • Active expiry: a background cycle samples keys with TTLs ~10×/sec and deletes the expired ones, adaptively working harder if it finds many.
  • So an expired key can occupy memory until either it's accessed or the active cycle reaches it. Usually negligible; with millions of short-TTL keys it can lag.

Eviction (maxmemory) and expiry (TTL) are independent — a key can be evicted before its TTL, or expire before being evicted.

Verification and troubleshooting

redis-cli INFO stats | grep -E 'keyspace_hits|keyspace_misses|evicted_keys|expired_keys'
redis-cli INFO memory | grep -E 'used_memory:|used_memory_rss:|maxmemory:|mem_fragmentation_ratio'
redis-cli --bigkeys
redis-cli MEMORY DOCTOR

Hit rate = keyspace_hits / (keyspace_hits + keyspace_misses).

  • OOM command not allowed when used memory > 'maxmemory' — policy is noeviction, or it's volatile-* and nothing has a TTL. Switch to allkeys-lfu, or add TTLs.
  • Low hit rate despite plenty of memory — TTLs too short, cache keys include something high-cardinality (a timestamp, a request id), or the app isn't actually reading from cache first. Log key patterns with redis-cli MONITOR (briefly — it's expensive).
  • evicted_keys climbing — working set exceeds maxmemory. Raise it, shard, shorten TTLs on bulky low-value keys, or store smaller values (compress, don't cache whole HTML).
  • mem_fragmentation_ratio > 1.5 — allocator fragmentation. Enable activedefrag yes, or restart. Ratio < 1 means Redis is swapping — bad.
  • Memory higher than the sum of key sizes — per-key overhead (~50–90 bytes), or large collections with many small elements. --bigkeys and MEMORY USAGE key.
  • Latency spikes on eviction — evicting large keys (a 1 GB hash) blocks. Break big structures up; lazyfree-lazy-eviction yes frees them in a background thread.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.