skip to content

For a Redis instance acting as a cache, how do you choose between the `allkeys-lru`, `allkeys-lfu` and `allkeys-random` values of `maxmemory-policy`? Give a workload where each is the better pick.

level: seniorimportance: should knowfreq 52%

answer

  1. LRU = recency, dies to batch scans
  2. LFU = frequency, scan-resistant, decays
  3. lfu-log-factor / lfu-decay-time
  4. random = uniform access or pure headroom
  5. decide with keyspace_hits vs misses

basics

~20 s

LRU suits recency-driven traffic but a batch scan can flush the hot set. LFU keeps stable popular keys and resists one-off scans, at the cost of adapting slower to shifting popularity. Random is cheapest and fine when access is near-uniform or keys are equally valuable.

solid answer

~50 s

All three evict from the whole keyspace; they differ in the victim heuristic. **`allkeys-lru`** favors recency. It fits sessions, feed pages and anything with strong temporal locality, where a key just used is likely to be used again. Its weakness is **scan pollution**: a nightly export or a crawler touching millions of cold keys makes them all "recent" and evicts the genuinely hot set, so hit rate collapses right after the batch job. **`allkeys-lfu`** favors access frequency with a decaying counter, so a key touched once by a scan stays a weak candidate. It is the better default for skewed popularity with a stable hot set — product catalogs, reference data, hot user profiles. It reacts more slowly when popularity genuinely shifts; `lfu-decay-time` and `lfu-log-factor` tune that. **`allkeys-random`** does no bookkeeping and is fine when access is close to uniform, or when you simply need headroom and hit rate is insensitive to which key goes. Decide with data: run both, compare `keyspace_hits`/`keyspace_misses` and `evicted_keys`.

code

text · 9 lines
text
127.0.0.1:6379> CONFIG SET maxmemory-policy allkeys-lfu
OK
127.0.0.1:6379> CONFIG SET lfu-decay-time 1     # faster adaptation to shifting popularity
OK
# after a full traffic cycle, incl. nightly batch:
127.0.0.1:6379> INFO stats
keyspace_hits:184320991
keyspace_misses:5120441
evicted_keys:9013223

go deeper

for a junior

Be able to say LRU evicts the least recently used, LFU the least frequently used, and random picks arbitrarily — and that all three can evict any key.

for a middle

Add the scan-pollution weakness of LRU, the decaying counter behind LFU, and that both are sampled approximations rather than exact orderings.

for a senior

Map a real workload to a policy, name the batch-job failure mode, and validate the choice with hit/miss and eviction counters across a full traffic cycle.

for a principal

Frame it as a cache-economics decision: what a miss costs downstream, whether memory or backend capacity is the scarcer resource, and whether the fix is policy, sizing, key design, or keeping batch traffic out of the cache entirely.

## What the suffix decides With an `allkeys-*` policy, every key is an eviction candidate; the suffix only decides **which candidate dies first**. Redis does not maintain exact orderings — it samples a few keys (`maxmemory-samples`, default 5) and evicts the best victim among them; LFU additionally keeps a small per-object counter. Those approximation mechanics are a separate subject. What you are being asked here is a *workload-fit* question: which heuristic predicts future access best for your traffic. ## `allkeys-lru` — recency LRU assumes the recent past predicts the near future. That assumption holds for a huge share of web workloads: a user who just loaded their dashboard will load it again; a session touched now will be touched in 30 seconds. Its characteristic failure is **cache pollution by scan**. Any operation that sweeps a large set of keys once — a nightly report, an analytics export, a search-index rebuild, a crawler, a cache-warm script gone wrong — marks all those keys as most-recently-used. They now sit at the front of the queue while the true hot set becomes "old" and is evicted. The signature is a hit-rate cliff that starts when the batch job starts and recovers slowly afterwards as the hot set is re-populated (each miss costing a backend query, which is exactly when the backend is already busy with the batch job). LRU is also weak at distinguishing *one* recent access from *thousands*: a key read once is indistinguishable from a key read constantly for the past hour. ## `allkeys-lfu` — frequency, with decay LFU tracks roughly how often a key is accessed, using a counter that both saturates logarithmically (so a key read a million times does not become immortal relative to one read a thousand times) and decays over time (so yesterday's hero can eventually be evicted). Two directives tune it: `lfu-log-factor` controls how fast the counter saturates, `lfu-decay-time` how many minutes of idleness halve it. This makes LFU **scan-resistant**: a batch job that touches a key once bumps its counter barely off the floor, so it remains a prime eviction candidate while the hot set survives. For workloads with a heavy popularity skew — a Zipf-like distribution where a small percentage of keys serves most traffic, e.g. product pages, reference data, top-N feeds — LFU typically beats LRU on hit rate at the same memory. The cost is **adaptation lag**. When popularity genuinely shifts — a flash sale changes which SKUs are hot, a new release changes which config is read — the old winners keep high counters until decay catches up, so the cache is briefly wrong about what to keep. Shortening `lfu-decay-time` trades scan-resistance back for adaptivity. LFU also spends slightly more per-object bookkeeping, though in practice the cost is negligible next to the hit-rate difference. ## `allkeys-random` — no heuristic Random eviction picks victims uniformly. It sounds like giving up, and for skewed workloads it is: it evicts hot and cold keys at the same rate. But it is genuinely the right answer in two cases. First, when access really is near-uniform — a large sharded lookup where no key is meaningfully hotter than another; then LRU/LFU bookkeeping buys nothing and random gives the same hit rate more cheaply and with perfectly even eviction pressure. Second, when the instance is not a hit-rate-optimizing cache at all but a bounded scratch space where you only need the memory ceiling enforced and any survivor is as good as any other. ## Choosing in practice 1. **Characterize the access distribution.** If a small key set serves most requests, lean LFU. If access is recency-driven with rolling working sets, lean LRU. If it is flat, random is defensible. 2. **Ask whether anything scans the keyspace.** Batch exports, warmers and crawlers are the single strongest argument for LFU. 3. **Measure, don't argue.** `INFO stats` gives `keyspace_hits`, `keyspace_misses` and `evicted_keys`. Run the candidate policy for a full traffic cycle (including the nightly jobs) and compare hit ratio and backend load. Policy is switchable live with `CONFIG SET maxmemory-policy …`, which makes an A/B on a replica-of-production or a canary node cheap. 4. **Watch for the wrong question.** A bad hit rate is frequently caused by an undersized instance, unbounded key growth, or a few enormous keys crowding out everything else — none of which a policy change fixes. Check that the working set plausibly fits in `maxmemory` before tuning the heuristic. Finally, remember all three make the instance fully disposable. If any key in it must not vanish, the eviction-family question (allkeys vs volatile vs noeviction) outranks this one.

  • A nightly export job iterates millions of rows and caches each one; the morning hit rate is terrible. What do you change?
    That is textbook LRU scan pollution: the export made millions of cold keys most-recently-used and pushed out the real hot set. Switching to `allkeys-lfu` makes a single touch barely raise a key's counter, so the batch keys stay prime eviction candidates. Complementary fixes: have the batch job not write to the cache at all, or write with a short TTL, so it stops competing for the same budget.
  • What do `lfu-log-factor` and `lfu-decay-time` control?
    `lfu-log-factor` sets how quickly the per-key frequency counter saturates — a higher factor means many more accesses are needed to move the counter, so hot keys are compressed into a narrower range. `lfu-decay-time` is the number of minutes of idleness after which the counter is halved, so it controls how fast a formerly popular key becomes evictable. Lower decay adapts faster to shifting popularity but gives up some scan resistance.
  • When is `allkeys-random` actually a reasonable production choice?
    When the access distribution is close to uniform, so no heuristic can predict the next access better than chance and the bookkeeping buys nothing; or when the instance is a bounded scratch space where enforcing the memory ceiling matters and any surviving key is as valuable as any other. It also spreads eviction pressure evenly, which avoids the pathological case of one heuristic repeatedly picking the same subset.

LRU is a desk where whatever you last touched sits on top — one afternoon of filing the whole archive buries the papers you actually use. LFU is a desk sorted by how often you reach for something, with an old-favourites shelf that slowly slides toward the bin.

saying these in an interview costs you the question

  • Claiming Redis maintains an exact LRU or LFU ordering rather than a sampled approximation.
  • Assuming LFU is strictly better than LRU, ignoring its slower adaptation when popularity shifts.
  • Believing a policy change can fix a hit rate that is really caused by an undersized instance or a few huge keys.
  • Saying random eviction is never acceptable, even for uniform access.
  • Choosing a policy from intuition alone without comparing `keyspace_hits`/`keyspace_misses` under real traffic.

context