Call
Redis

Running Redis as a Cache: maxmemory, Eviction Policies, TTLs, and Monitoring

Operate Redis as a bounded cache by choosing maxmemory and the right LRU/LFU/LRM or volatile eviction policy, separating TTL from eviction, and monitoring thrashing, memory, hits, misses, and evictions.

Published: July 10, 2025
Updated: September 12, 2026
10 min read
Running Redis as a Cache: maxmemory, Eviction Policies, TTLs, and Monitoring

Running Redis as a Cache: maxmemory, Eviction Policies, TTLs, and Monitoring

Redis is often introduced as a fast key-value store, but operating it as a bounded cache requires a few deliberate choices. You need to decide how much RAM the dataset may use, what Redis should evict when that limit is reached, whether cache entries also need TTLs, and which signals tell you the cache is too small or badly shaped.

This article is about that operational layer. It intentionally does not repeat cache-aside/invalidation strategy guidance covered elsewhere on Lofingo.

A cache needs a memory boundary

For a dedicated Redis cache, maxmemory defines the memory limit Redis uses when deciding whether the dataset has grown too large.

A simple configuration might be:

maxmemory 4gb
maxmemory-policy allkeys-lru

Once memory crosses the configured limit, Redis applies the selected eviction policy as new data is added.

Do not blindly set maxmemory equal to all RAM available on the machine. Redis may also need memory for areas such as:

  • replication buffers
  • AOF-related buffers
  • client connections
  • allocator overhead and fragmentation
  • operating-system headroom

Redis' eviction documentation specifically recommends leaving room for replication or persistence buffers. INFO memory exposes mem_not_counted_for_evict, which helps show memory used by buffers outside the dataset amount considered for eviction.

TTL and eviction solve different problems

A TTL says:

This cached item should stop existing after a chosen amount of time.

Eviction says:

If Redis runs out of the configured cache memory, which existing item should be removed to make room?

A key can therefore:

  • expire because its TTL reaches zero
  • be evicted earlier because memory is full
  • remain indefinitely if it has no TTL and the eviction policy does not remove it

For freshness-sensitive caches, TTLs are still useful even when an LRU/LFU policy is enabled. Eviction should not be your only freshness mechanism.

The main eviction policy families

Redis exposes several maxmemory-policy choices. The important distinction is whether all keys may be evicted or only keys with an expiry.

allkeys-lru

Evicts keys that Redis estimates were least recently used.

Useful when:

  • Redis is primarily a cache
  • recent access is a reasonable signal of future usefulness
  • any cached key may be discarded and rebuilt

Redis uses an approximated LRU algorithm rather than maintaining a perfect global LRU list, avoiding the extra memory cost of exact tracking.

allkeys-lfu

Evicts keys that are estimated to be used least frequently.

Useful when:

  • a relatively stable hot set receives repeated requests
  • frequency is a better predictor than recency
  • you want frequently requested items to survive occasional scans or bursts of one-off keys

Redis' LFU implementation uses an approximate frequency counter with decay so historical popularity does not last forever.

allkeys-lrm

Current Redis Open Source documentation includes Least Recently Modified (LRM) policies, introduced in Redis 8.6.

LRM updates its recency signal on writes rather than reads. That makes it different from LRU: frequently read but rarely modified keys may still become eviction candidates according to modification age.

This can be useful for workloads where update recency is the signal you care about, but it is not automatically a better general-purpose cache policy than LRU or LFU.

allkeys-random

Evicts keys randomly.

It can be reasonable when access distribution is close to uniform and tracking recency/frequency provides little value.

volatile-* policies

Policies such as:

  • volatile-lru
  • volatile-lfu
  • volatile-lrm
  • volatile-random
  • volatile-ttl

only choose among keys that have an expiration configured.

These are useful when one Redis database intentionally contains both evictable TTL-bearing data and keys that should not be evicted.

But Redis' own documentation suggests considering separate Redis instances when mixing persistent data and disposable cache data. Separate workloads are often easier to reason about operationally.

noeviction

With noeviction, Redis does not make room by deleting dataset keys. Commands that need to add data can fail when the memory limit is reached, while reads can continue.

That behavior is appropriate when automatic data loss is unacceptable. It is usually not the desired policy for a pure reconstructable cache.

volatile-ttl is not the same as normal expiry

volatile-ttl chooses eviction candidates among expiring keys based on how soon their TTL will end.

The idea is that keys already near natural expiry are cheaper to discard early.

This policy is most useful when your application deliberately assigns meaningful TTL differences. If every key has nearly the same TTL, it provides less useful prioritization.

Choose policy from the access pattern

There is no universal winner.

WorkloadPolicy worth evaluating
general cache with a hot recent setallkeys-lru
stable frequently reused hot setallkeys-lfu
modification recency is importantallkeys-lrm on supported Redis versions
roughly uniform/cyclic accessallkeys-random
only TTL keys are disposableappropriate volatile-* policy
no automatic dataset loss allowednoeviction

Redis' documentation calls allkeys-lru a useful default when a subset of keys receives much more traffic than the rest and you have no stronger reason to prefer another policy.

Treat that as a starting hypothesis, then verify against production hit rate and eviction behavior.

Do not confuse LRU with a guarantee

Redis LRU and LFU are approximation algorithms. A recently used key is not absolutely guaranteed to survive, and the exact victim is not computed from a perfect globally ordered list.

That trade-off is intentional: exact tracking would cost additional memory and CPU.

If a particular key must never disappear, it should not be modeled as disposable cache data under an all-keys eviction policy.

Set TTLs based on freshness, not memory anxiety

A common anti-pattern is assigning very short TTLs only because Redis memory feels expensive.

TTL should primarily express how long an entry may remain useful or safely stale. Memory pressure is better controlled with:

  • realistic maxmemory
  • a fitting eviction policy
  • smaller cached objects
  • avoiding low-value entries
  • sufficient capacity

If a product record may safely be cached for 30 minutes, setting a 10-second TTL can destroy hit rate and repeatedly hammer the database even when plenty of cache memory is available.

Watch for cache thrashing

A cache is thrashing when useful entries are evicted so rapidly that requests repeatedly fetch the same data from the origin, repopulate Redis, and evict something else.

Typical symptoms include:

  • rising evicted_keys
  • poor or falling hit rate
  • higher origin/database traffic
  • increased application latency
  • Redis sitting continuously near its memory limit

Redis' observability guidance specifically calls out the combination of low hit ratio and high eviction volume as a sign that the cache may be too small.

Fixes may include:

  • increasing capacity
  • caching fewer low-value objects
  • reducing object size
  • choosing a better policy
  • separating workloads with different lifecycles
  • fixing pathological key-cardinality growth

Monitor expired_keys and evicted_keys separately

INFO stats exposes counters including:

  • expired_keys — keys removed because expiry elapsed
  • evicted_keys — keys removed because of the maxmemory limit

Those numbers tell different stories.

A high number of expirations may be entirely healthy in a TTL-heavy cache. A sudden increase in evictions may mean the working set no longer fits.

Also observe:

  • cache hits and misses
  • used memory
  • memory fragmentation
  • latency
  • operation rate
  • origin fallback traffic
  • largest/high-cardinality key families

No single metric tells you whether the cache is successful.

Hit rate is useful, but not enough

Suppose Redis shows a high hit ratio because millions of cheap requests hit the cache, while one expensive uncached query still saturates the database. The overall percentage looks excellent but the system remains slow.

Evaluate caching by the work avoided:

  • which origin operations disappeared?
  • how much latency was removed?
  • which expensive endpoints still miss?
  • are hot keys staying resident?
  • is memory spent on entries that are actually reused?

A smaller high-value cache can outperform a much larger cache filled with rarely reused data.

Memory usage is more than serialized payload size

A common sizing mistake is:

average JSON size × number of keys = required Redis RAM

Real memory usage also includes:

  • key objects and metadata
  • data-structure overhead
  • expiry metadata
  • allocator behavior
  • fragmentation
  • secondary indexes or Sets
  • replication/persistence buffers outside the evictable dataset calculation

Load-test representative objects rather than estimating only from raw JSON payload size.

Large values can destabilize an otherwise healthy cache

A few unusually large values can consume substantial memory, increase network transfer time, and cause more useful small entries to be evicted.

Before caching a large result, ask:

  • is it requested often enough to justify the space?
  • can the response be paginated?
  • can smaller components be cached instead?
  • would a precomputed/materialized store be more appropriate?

Do not let Redis become an accidental dump for every expensive response.

Avoid unbounded key cardinality

Even if each cached entry is small, a key space driven by unbounded combinations can grow explosively.

Examples include cache keys containing:

  • arbitrary search text
  • timestamps with excessive precision
  • random request identifiers
  • every ordering of the same query parameters
  • user-controlled values without normalization

Normalize inputs and deliberately decide which dimensions belong in the key. Otherwise the cache can have a terrible reuse rate while consuming large amounts of memory.

Separate cache data from state with different failure semantics

Redis is frequently used for more than caching: sessions, locks, rate limits, queues, pub/sub, and durable-ish application state.

If those workloads share one memory limit and an allkeys-* policy, memory pressure from the cache can evict keys that were never intended to behave like cache entries.

A clean architecture often separates workloads when:

  • data-loss expectations differ
  • persistence settings differ
  • eviction behavior differs
  • latency/noisy-neighbor risk differs
  • scaling patterns differ

Logical prefixes help organization, but they do not create separate maxmemory pools inside one Redis process.

Leave headroom for replication and persistence

When replication or AOF persistence is enabled, Redis can need additional memory for buffers. The eviction documentation explicitly recommends leaving RAM outside maxmemory for those needs.

Use INFO memory and observe real peaks rather than assuming the configured dataset ceiling is the process's entire memory requirement.

A cache that is stable at average usage but repeatedly approaches host OOM during synchronization or traffic spikes is not correctly sized.

Runtime configuration needs persistence too

Redis allows many settings to be changed at runtime with CONFIG SET, but changing runtime configuration does not automatically mean your configuration file has been updated for the next restart.

Redis documentation recommends updating the config file or using CONFIG REWRITE when appropriate so the intended settings survive restart.

That matters for operationally critical values such as maxmemory and eviction policy.

Managed Redis services expose these settings differently, so use the provider's supported configuration interface rather than assuming every Open Source command is available.

A practical dedicated-cache baseline

For a Redis instance whose data is fully reconstructable, a reasonable starting process is:

  1. Measure the expected hot working set.
  2. Set maxmemory below total available RAM, leaving operational headroom.
  3. Start with a policy that matches the access pattern; allkeys-lru is a common general baseline.
  4. Add TTLs wherever freshness requires them.
  5. Monitor hits, misses, expirations, evictions, memory, fragmentation, and origin load.
  6. Load-test realistic key sizes and cardinality.
  7. Revisit LRU vs LFU vs other policies using actual workload evidence.
  8. Separate non-cache state if its eviction or durability requirements differ.

Redis caching works best when memory pressure is an expected, observable operating condition, not an emergency discovered only after the server starts dropping useful data.

Official references

Tags:RedisCachingEvictionMemory ManagementPerformanceObservability
ENGINEERING & PRODUCT DESIGN

BUILDING A DIGITAL PRODUCT?

Talk to Lofingo about modern web apps, high-performance software engineering, and production digital platforms.

EXPLORE SERVICES →