Chapter 18 · Memory Management, Eviction, Expiration, Fragmentation, and OOM Prevention
Approximate LRU/LFU Sampling, LFU Decay, Hot-Set Behavior, and Cache Hit Rate
Understand Redis approximate LRU/LFU sampling, LFU decay and probabilistic counters, then measure hot-set survival and cache hit rate under controlled pressure.
Learning outcomes
AtlasMart has a small hot set of products and a long cold tail. The team wants “perfect LRU,” but perfect global ordering would itself consume CPU/memory. Redis instead samples candidates and uses compact metadata, so the useful question is whether the approximation preserves the workload's valuable working set.
Explain sampled LRU and why maxmemory-samples changes accuracy/CPU cost rather than correctness semantics.
Explain LFU probabilistic counters, lfu-log-factor, and decay.
Use OBJECT FREQ and OBJECT IDLETIME only under policies where they are valid.
Measure cache hit rate and hot-set survival under deterministic skew.
Avoid treating one key's metadata as a guarantee of exact eviction order.
The course baseline remains Redis Open Source
8.10.1 from pinned image
redis:8.10.1. Memory-pressure experiments use
dedicated disposable Chapter 18 containers, named volumes, and
loopback-only host ports 6391–6395 so the shared Chapter 01
lab is never pushed toward eviction or out-of-memory behavior.
The temporary academy-admin password is
classroom-only. TLS is omitted only on loopback. Mandatory
topology is standalone, logical DB 0. Each lesson states its
own persistence, maxmemory, eviction, and container-memory
settings. Lesson 3 uses a 24 MiB maxmemory node with
persistence disabled. The fixture is intentionally small and
records actual results rather than claiming a universal best
sample size.
1. LRU is sampled, not a global exact list
Redis LRU samples a small number of keys, keeps good candidates,
and evicts among those candidates rather than maintaining a
perfect ordering of every key.
maxmemory-samples controls the sample count. Larger
samples can more closely approximate ideal LRU but consume
additional CPU during eviction.
“Key A is older than key B” does not guarantee A is the next key evicted. The policy is probabilistic at the candidate-selection level.
2. Start with LFU and inspect its knobs
docker volume create atlasmart-redis-ch18-policy_datadocker run --rm -v atlasmart-redis-ch18-policy_data:/data redis:8.10.1 sh -lc 'printf "%s\n" "user default off" "user academy-admin on >AtlasMart-Admin-Lab-Only-2026 ~* &* +@all" > /data/users.acl'docker run -d --name atlasmart-redis-ch18-policy --memory 192m -p 127.0.0.1:6393:6379 -v atlasmart-redis-ch18-policy_data:/data redis:8.10.1 redis-server --dir /data --aclfile /data/users.acl --save '' --appendonly no --maxmemory 24mb --maxmemory-policy allkeys-lfu --maxmemory-samples 5 --lfu-log-factor 10 --lfu-decay-time 1docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-policy redis-cli --user academy-admin PINGdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-policy redis-cli --user academy-admin ACL WHOAMI
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-policy redis-cli --user academy-admin CONFIG GET maxmemory maxmemory-policy maxmemory-samples lfu-log-factor lfu-decay-timedocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-policy redis-cli --user academy-admin INFO statsdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-policy redis-cli --user academy-admin INFO memory
3. LFU stores a compact probabilistic frequency estimate
LFU uses a logarithmic Morris-style counter rather than an exact
request count, then decays old popularity over time. The default
lfu-log-factor 10 controls counter growth and
lfu-decay-time 1 means one-minute decay intervals
when sampled. The counter is an eviction heuristic—not an
analytics metric.
import os, redisHOST = "127.0.0.1"PORT = 6393r = redis.Redis(host=HOST, port=PORT, username="academy-admin", password="AtlasMart-Admin-Lab-Only-2026", decode_responses=False)assert r.ping()import randomrng = random.Random(1803)payload = b"v" * 16384for i in range(500): r.set(f"atlasmart:ch18:l3:item:{i}", payload)hot = list(range(20))warm = list(range(20, 100))for _ in range(6000): x = rng.random() if x < 0.75: i = rng.choice(hot) elif x < 0.95: i = rng.choice(warm) else: i = rng.randrange(100, 500) r.get(f"atlasmart:ch18:l3:item:{i}")print("hot_freq", [r.object("freq", f"atlasmart:ch18:l3:item:{i}") for i in range(5)])print("cold_freq", [r.object("freq", f"atlasmart:ch18:l3:item:{i}") for i in range(450,455)])
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-policy redis-cli --user academy-admin OBJECT FREQ atlasmart:ch18:l3:item:0docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-policy redis-cli --user academy-admin OBJECT FREQ atlasmart:ch18:l3:item:450docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-policy redis-cli --user academy-admin --hotkeys --pattern "atlasmart:ch18:l3:*" --count 100
4. OBJECT FREQ and IDLETIME are policy-specific diagnostics
OBJECT FREQ is available only under LFU policies.
OBJECT IDLETIME is unavailable when the maxmemory
policy is LFU because the compact object metadata is used for
LFU frequency instead. Always verify policy before interpreting
either command.
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-policy redis-cli --user academy-admin OBJECT FREQ atlasmart:ch18:l3:item:0docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-policy redis-cli --user academy-admin OBJECT IDLETIME atlasmart:ch18:l3:item:0# Under an LFU policy, OBJECT IDLETIME should report that it is unavailable.
5. Pressure the cache and measure population-level outcomes
The right test is not “did the mathematically oldest key disappear?” It is “under this reproducible access distribution and memory pressure, did the policy preserve the hot working set and deliver an acceptable miss/latency profile?”
import os, redisHOST = "127.0.0.1"PORT = 6393r = redis.Redis(host=HOST, port=PORT, username="academy-admin", password="AtlasMart-Admin-Lab-Only-2026", decode_responses=False)assert r.ping()stats0 = r.info("stats")hits0, misses0 = stats0["keyspace_hits"], stats0["keyspace_misses"]payload = b"n" * 16384for i in range(500, 1800): r.set(f"atlasmart:ch18:l3:item:{i}", payload)alive_hot = sum(r.exists(f"atlasmart:ch18:l3:item:{i}") for i in range(20))alive_cold = sum(r.exists(f"atlasmart:ch18:l3:item:{i}") for i in range(450,500))for i in range(20): r.get(f"atlasmart:ch18:l3:item:{i}")for i in range(450,500): r.get(f"atlasmart:ch18:l3:item:{i}")stats1 = r.info("stats")dh = stats1["keyspace_hits"] - hits0dm = stats1["keyspace_misses"] - misses0print("hot_alive", alive_hot, "of 20", "cold_alive", alive_cold, "of 50")print("probe_hit_rate", dh / max(1, dh + dm), "evicted_keys", stats1["evicted_keys"])
This fixture is not a production benchmark. Real hit rate depends on request skew, object-size distribution, TTLs, pipeline/concurrency, sample size, and client/server topology.
6. Change maxmemory-samples only as an experiment
The default sample count is a deliberate efficiency tradeoff. Increasing it can make LRU/LRM candidate selection closer to an ideal ordering, but more sampling adds CPU work. For LFU, sampling is only one part of behavior; the logarithmic counter and decay parameters also matter.
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-policy redis-cli --user academy-admin CONFIG GET maxmemory-samplesdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-policy redis-cli --user academy-admin CONFIG SET maxmemory-samples 10docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-policy redis-cli --user academy-admin CONFIG GET maxmemory-samplesdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch18-policy redis-cli --user academy-admin CONFIG SET maxmemory-samples 5
Change one parameter at a time and compare hit/miss distribution and tail latency. Do not copy a universal sample count from a benchmark.
7. LFU decay is adaptation, not a timer that resets counters exactly
Decay prevents yesterday's hot key from staying protected
forever. Redis applies decay as keys participate in the LFU
mechanism; lfu-decay-time should not be interpreted
as a background scheduler that resets every counter at an exact
wall-clock boundary. A value of zero disables decay.
| Setting | Meaning | Risk if misread |
|---|---|---|
| lfu-log-factor | Controls how quickly the probabilistic counter grows/saturates. | Treating FREQ as exact request count. |
| lfu-decay-time | Minutes controlling frequency decay when relevant metadata is sampled. | Assuming synchronized periodic decrements for every key. |
| maxmemory-samples | Candidate sample count for approximate eviction. | Assuming larger is always better regardless of CPU/tail latency. |
8. Redis 8.6+ LRM is a separate question
Least Recently Modified (LRM) was added in Redis 8.6. It tracks write recency rather than read+write access recency. That can be useful for read-heavy datasets where frequently read but stale values should still be considered old. It is not a replacement for LRU or LFU; it expresses a different notion of value.
9. Verification checklist
- Confirm policy before using OBJECT FREQ or OBJECT IDLETIME.
- Capture frequency counters as heuristic metadata, not exact request counts.
-
Measure
keyspace_hits/keyspace_missesdeltas andevicted_keys. - Compare hot-set and cold-tail survival under the same deterministic trace.
- Do not change LFU decay/log-factor or sample size without latency and workload evidence.
docker rm -f atlasmart-redis-ch18-policydocker volume rm atlasmart-redis-ch18-policy_data# Removes only this named Chapter 18 node and volume.
Check your understanding
- Why does Redis approximate LRU instead of maintaining a perfect global order?
- Is OBJECT FREQ an exact access count?
- Why is OBJECT IDLETIME unavailable under LFU?
- What should decide maxmemory-samples?
Review the answers
Approximation greatly reduces metadata and CPU cost while preserving useful recency behavior for common workloads.
No. LFU uses a logarithmic probabilistic counter with decay; it is an eviction heuristic.
The compact per-key metadata used for idle-time/LRU tracking is repurposed for LFU frequency state.
Measured hit/miss behavior and latency/CPU under the real access and object-size distribution, not a universal number.
Production judgment and next bridge
Cache quality is a distribution-level property. Measure hit rate, tail latency, evictions, skew, and memory together. Lesson 4 moves from policy heuristics to pathological shapes: big keys, hot keys, asynchronous freeing, fragmentation, and active defrag diagnostics.
Summary and next step
Approximate LRU/LFU Sampling, LFU Decay, Hot-Set Behavior, and Cache Hit Rate is now connected to observable Redis behavior, bounded failure cases, and production tradeoffs. Keep the evidence and cleanup state from this lesson; next, continue with Big Keys, Hot Keys, Large Collections, Lazy Freeing, Active Defrag, and Memory Diagnostics.
Authoritative references
- Redis key eviction
- Redis memory optimization
- INFO
- MEMORY STATS
- MEMORY USAGE
- MEMORY DOCTOR
- MEMORY PURGE
- MEMORY MALLOC-STATS
- OBJECT FREQ
- OBJECT IDLETIME
- UNLINK
- Redis CLI keyspace analysis
- Redis latency monitoring
- Redis persistence
- Redis replication
- Redis Sentinel
- Redis Cluster specification
- Redis Search and query
- Redis 8.10 release notes
- Redis 8.10 whats new
- Redis ACLs
- Redis security
- Redis licenses
- Docker Official Redis image