...

Redis LFU vs. LRU: Which Eviction Policy Is the Right One?

Redis LFU and LRU determine which keys are evicted from the cache when resources are scarce—and thus determine Hit rate, response time, and memory usage. I'll show you when the frequency-based LFU policy or the recency-based LRU policy is more appropriate, how to configure them, and what effects "allkeys-lfu" versus "allkeys-lru" have in everyday use; the focus keyword Redis LFU is at the heart of this.

Key points

  • Recency vs. Frequency: LRU favors the most recent accesses; LFU favors frequent accesses.
  • Approximation In Redis: Both policies use samples based on `maxmemory-samples`.
  • Workloads Choose: Sessions/Dashboards → LRU, Bestsellers/Rankings → LFU.
  • Tuning Make sure to set lfu-decay-time, maxmemory, and maxmemory-samples correctly.
  • Monitoring Required: Continuously monitor the hit rate, evictions per second, and latency.

How Eviction Works in Redis

Redis stores data in RAM; if the process maxmemory, it must evict keys. This is exactly where policies like allkeys-lru and allkeys-lfu come into play; they determine which entries are evicted. I’m focusing on these two variants because they take the entire dataset into account, not just keys with a TTL. Redis selects the key to be deleted via a sample, which you can configure with maxmemory-samples controls; more samples increase accuracy but consume CPU resources. This approach yields good results in large keyspaces without making management too costly.

Behind the Scenes: How Redis Implements LRU and LFU

Both policies run in Redis approximately, to maintain a consistently high speed. LRU stores a timestamp for the last access for each object. During eviction, Redis takes a sample and discards the „oldest“ candidate from the selection. In practice, this is extremely efficient and sufficiently accurate if you choose a sample size appropriate for the keyspace.

Redis LFU expands on this idea by adding a Compact Frequency Meter, which over time ages (Decay). Each access increases the usage counter not linearly, but in a damped manner, so that individual burst phases do not permanently saturate the counter. At the same time, a time decay ensures that past popularity eventually loses its weight. Through parameters such as lfu-decay-time (how quickly the history ages) and an internal log factor (how much the counters grow with each access)—you balance these responsiveness against Stability Prioritization. Rule of thumb: Smaller decay values → faster adjustment; larger values → slower but more stable priorities.

LRU in Redis: Principles, Strengths, Pitfalls

LRU removes the oldest one unused It uses a key-based approach, thereby prioritizing recency. This logic aligns well with patterns involving temporal locality, such as sessions, live dashboards, or short-term API responses. Redis uses an approximated LRU: Entries carry a timestamp, and sampling selects the oldest candidate—quickly and transparently. LRU reacts quickly to changes because recently used keys remain at the top while older ones are evicted. Large one-time scans can become problematic, as they fill the cache with short-lived values and push out important keys that are temporarily inactive suppress.

Practical tip: If you use LRU and regularly run „cold“ bulk queries (e.g., back-office reports), encapsulate these workloads in separate Caches, or plan for more generous ones maxmemory-Reserves. This helps you avoid cache pollution, in which valuable data that will be needed again soon is displaced.

LFU in Redis: Principles, Strengths, Pitfalls

LFU removes keys with low Frequency of Use and thus preserves long-term „hot keys.“ The internal counter grows logarithmically and decays over time so that past popularity doesn’t count indefinitely. This results in a balancing weighting: Frequently used data is retained longer, while individual outliers have little effect on priority. LFU often delivers a higher hit rate in catalogs, rankings, or feature caches because it keeps proven keys in memory. However, it reacts more slowly to new trends, which is why tuning lfu-decay-time remains important.

For On/Off Trends (e.g., marketing campaigns): Set the decay rate so that a new trend has a noticeable impact without short-term noise constantly resorting the cache. In many projects, the following approach has proven effective: start conservatively, then gradually accelerate until the hit rate remains stable under load.

Comparison: Recency vs. Frequency in Everyday Life

Essentially, LRU distinguishes between „last used“ and LFU distinguishes between „how often used“—I make my selection based on the actual Workloads. For volatile, user-centric data, LRU usually feels more natural, since recent accesses often foreshadow future accesses. For popular product data or configurations, LFU works better because sustained popularity is what matters. In mixed scenarios, I separate caches by data type and apply different policies. The following table briefly summarizes the differences and gives you a quick Decision support.

Aspect LRU (allkeys-lru) LFU (allkeys-lfu)
Priority Actuality the number of hits Frequency the number of hits
Response to a Pattern Change Hurry—the last time you used it counts Moderate, since history plays a role
Recommended workloads Sessions, Dashboards, Live APIs Bestsellers, Rankings, Feature Caches
Sensitivity to „Pollution“ Tends to be high for large scans Rather low due to the frequency counter
Tuning Screws maxmemory-samples lfu-decay-time, maxmemory-samples
Explainability Very intuitive Well, with regard to Decay

Performance Impacts in Practice

With small data sets, the difference is often low; as the dataset grows, the wheat is separated from the chaff. LRU stands out due to its low CPU overhead for approximation and a clear rationale: a key is evicted because it was last left unused. LFU excels with consistent access patterns, as hot keys remain securely in RAM and the hit rate measurably increases. The trade-off lies in the necessary understanding of counters and decay, so that you react neither too sluggishly nor too aggressively. I verify the effects using profiling and metrics, rather than relying solely on intuition. decide.

Also, plan for the cold start First: After a reboot or deployment, the cache is empty or „unaware“ of access frequencies. LRU quickly stabilizes based on short-term locality. LFU naturally requires some warm-up time to identify true hot keys. Strategies such as Prewarming (proactively loading important keys) or staggered traffic ramping can help mitigate initial latency and misses.

Configuration and Tuning: The Most Important Options

I select the policy via maxmemory policy, typically allkeys-lru or allkeys-lfu, and less commonly volatile variants with a TTL focus. With maxmemory I set the hard threshold at which eviction begins and scale it based on the dataset size plus a safety margin. I control the sample size via maxmemory-samples; higher values improve the selection but require more CPU power. For LFU, lfu-decay-time This is crucial because it determines how quickly old accesses fade and new ones gain weight. You can find detailed instructions on memory sizing here: Optimize Storage Configuration.

Practical Tips for Everyday Use

To get started quickly, I work with clear defaults and iterate under load:

  • allkeys-lru + maxmemory-samples 7–10 for volatile, user-facing data
  • Redis LFU (allkeys-lfu) + lfu-decay-time set to a conservative value (e.g., a moderate value) for stable hotkey workloads

Set the configuration at runtime:

CONFIG SET maxmemory 8gb
CONFIG SET maxmemory-policy allkeys-lru
CONFIG SET maxmemory-samples 10
# Switch to LFU:
CONFIG SET maxmemory-policy allkeys-lfu
CONFIG SET lfu-decay-time 5

In redis.conf, you define these same options permanently. I test changes first in staging with a representative load before deploying them to production.

Select the sample size

maxmemory-samples This is a reliable tuning parameter: Higher values improve the accuracy of eviction candidates but consume CPU resources. As a rule of thumb, I start with 7–10 for large keyspaces and only reduce this value if CPU time becomes scarce. For small keyspaces, 5 samples are often sufficient.

Monitoring and Metrics: Measure Instead of Guess

I am constantly observing Hit rate, evictions, latencies, and memory usage to assess their interaction. If evictions rise sharply, I check RAM reserves, TTL strategies, and the selected policy. A declining hit rate often indicates that changing patterns are weakening the current policy or that data sets are not being cached separately enough. Latency spikes sometimes indicate that Samples or toward overly aggressive eviction. Regular load tests help me find the right balance between CPU usage, memory limits, and hit rate.

Handy commands for quick checks:

INFO stats     # keyspace_hits, keyspace_misses, evicted_keys, expired_keys
INFO memory    # used_memory, fragmentation, allocator_overhead
LATENCY DOCTOR # Notes on spikes, e.g., forking or I/O

The Hit rate I calculate it as hits / (hits + misses). A declining ratio amid rising evictions is a warning sign. evicted_keys in relation to traffic and used_memory indicates whether the policy needs to be triggered frequently. With MEMORY USAGE key You can identify oversized objects that disproportionately dominate your cache.

Hosting and Scaling Considerations: Choose Your Platform Carefully

Redis really shines in a high-performance A platform with plenty of RAM, low latency, and a reliable network connection. As projects grow, I avoid continuous operation at full capacity because eviction then triggers too frequently and the hit rate suffers. A good Hosting strategy ensures that policies kick in when necessary and don't run constantly. When comparing options, I rely on premium providers like webhoster.de, whose infrastructure handles high loads smoothly and enables predictable capacity. As a result, the platform directly benefits from fewer evictions and better Response times and more consistent performance.

Cluster and Replica Considerations

In sharding setups (e.g., Redis Cluster), eviction decisions take effect per node. This means that headroom, policy, and tuning must be appropriate for each node, not just „on average.“ Hot keys that are unevenly distributed across slots can push individual nodes to their limits sooner. Therefore, plan buffers per shard and monitor evictions at the node level. Replicas inherit the data state, including deleted keys; during load testing, keep in mind that additional replication can increase latency without the policy itself being at fault.

TTL Strategies and Mixed Policies

With TTL, I protect durable Configurations and prioritize time-sensitive, short-lived data. If I use volatile-lru or volatile-lfu, Redis only evicts keys that have expired—which is helpful when cache and persistent values coexist. I often separate caches by data type: sessions on LRU, product catalogs on LFU, to take advantage of their respective strengths. A smart TTL choice prevents outdated entries from unnecessarily tying up RAM and triggering evictions. This way, I keep memory clean without losing useful Hot Keys to lose.

Important: A policy applies per instance. You can reliably implement different policies for each data type by running separate Redis instances or clearly delineated caches. Namespaces alone do not change the policy; however, they help with targeted invalidation and measurement.

Practical Test: Start with LRU, then switch to LFU

I often start with LRU, because it’s intuitive and delivers quick results. Afterward, I identify caches with consistent hot keys and selectively switch to LFU. This approach minimizes risk because you only make changes where data patterns truly benefit from the frequency-based logic. Using canaries and A/B tests, I measure hit rates and latency before and after the switch. This way, I optimize step by step, rather than changing the entire Platform to switch over all at once.

A proven migration path

  • Establish a baseline: Record the current hit rate, evictions, and the 95th and 99th percentiles of latency.
  • Select a pilot cache: a stable, read-heavy area with clear hotkeys.
  • Enable LFU, lfu-decay-time set conservatively, maxmemory-samples increase.
  • Plan for a warm-up phase and monitor the process until the counters have stabilized.
  • Compare metrics, and only then make small adjustments.

Common Pitfalls in Apps (e.g., WordPress)

In content management systems, incorrect TTLs and unsuitable Keys This can quickly lead to a flood of evictions. Check whether dynamic pages are being cached unintentionally or whether values that are too large are causing the memory to overflow. Ensure proper invalidation behavior after publications so that outdated content is removed and space is freed up. This guide will help you identify typical error scenarios in a CMS environment: Object Cache Error. If you invalidate properly, set realistic TTLs, and choose the right policy, the hit rate and Speed measurable.

Additional anti-patterns from real-world experience:

  • Large Individual Properties (e.g., huge JSON blobs) crowd out many small, useful keys. Solution: Split the data into smaller chunks and cache only the segments that are actually used.
  • Thundering stove: Many simultaneous misses for the same key. Solution: Request coalescing/locks, short jitter in TTLs so that renewals occur in a distributed manner.
  • Scan Pollution: Batch reads without reuse. Solution: Separate instance/namespace, use LRU there with more generous memory, or deliberately avoid caching those workloads.
  • Unclear Invalidation: Old versions fill up the cache. Solution: Clear key schemes (e.g., version prefixes) and deterministic invalidation paths.

Summary: How I make the choice

I set LRU when recency provides the best heuristic for future access—such as with sessions, dashboards, and live APIs. I’ll use LFU, ...when there are clear, persistent hot keys that I want to protect even during peak loads. Monitoring shows me whether evictions are getting out of hand or the hit rate is dropping; then I adjust samples, TTLs, and decay. With a well-chosen platform, a smart memory limit, and separate caches for each data type, I consistently get more out of the system. This keeps the cache fast, predictable, and aligned with the access pattern—without any guesswork.

Current articles