What Is a Redis Eviction Policy?

A Redis eviction policy is the rule that decides which keys Redis removes when it reaches its memory limit. It is set with maxmemory-policy, it takes effect only once maxmemory is reached, and the default — noeviction — removes nothing at all, rejecting the writes that would need the memory instead.

Those two settings are independent, and both have to be set for eviction to happen. maxmemory defaults to 0, meaning no limit, so choosing a policy on its own changes nothing: there is no ceiling for it to react to. Configuring one without the other leaves you with a server that behaves nothing like the cache you thought you had built.

maxmemory and maxmemory-policy: The Two Settings

In redis.conf, both are one line each:

maxmemory 4gb
maxmemory-policy allkeys-lru

Both are also modifiable at runtime, with no restart:

redis-cli CONFIG SET maxmemory 4gb
redis-cli CONFIG SET maxmemory-policy allkeys-lru

Before relying on that, know two things. A CONFIG SET is not persisted — it is lost on restart unless you follow it with CONFIG REWRITE or edit the config file to match — and CONFIG REWRITE itself fails outright on a server started without a config file, which is the normal case for a container configured through command-line flags. And lowering maxmemory below current usage takes effect immediately: Redis starts evicting on the spot and writes a warning to the log saying so. That is usually what you want, but it is not a change to make casually on a live instance.

One edge case that catches people on older hardware and small containers: on a 32-bit build with no maxmemory set, Redis does not run unlimited. It imposes a 3 GB limit with noeviction and says so in a startup warning that is easy to miss. On 64-bit, 0 genuinely means unlimited — until the operating system's OOM killer disagrees.

All Ten Eviction Policies Compared

Redis and Valkey have diverged here, and most documentation written before 2026 is now incomplete. Redis has ten policy values as of 8.6; Valkey has eight. Six rows cover all ten, because every policy except volatile-ttl comes as an allkeys- and a volatile- pair — volatile-ttl has no allkeys- form, since comparing time-to-live requires a TTL to compare.

PolicyEvictsAvailable in
noeviction (default)Nothing — allocating writes are rejected insteadBoth
allkeys-lru / volatile-lruThe least recently used keyBoth
allkeys-lfu / volatile-lfuThe least frequently used keyBoth
allkeys-random / volatile-randomAn arbitrary keyBoth
volatile-ttlThe key closest to expiringBoth
allkeys-lrm / volatile-lrmThe least recently modified keyRedis 8.6+ only

Whichever value you pick governs the entire instance — one policy per server, not one per keyspace or collection. The allkeys- prefix means the policy considers every key in the keyspace. The volatile- prefix restricts it to keys that carry a TTL — which is a much sharper constraint than it looks, and the subject of the next section.

When the policy cannot free enough memory, a write is rejected with an error that is worth recognizing on sight:

OOM command not allowed when used memory > 'maxmemory'.

Note carefully which commands that applies to. Redis rejects only the commands flagged as needing to allocate — SET, INCR, APPEND, HSET and their kind. Reads and non-allocating writes keep working: GET and EXISTS succeed, and so do DEL, UNLINK and EXPIRE. That is not a technicality — it is the reason you can dig an instance out of this state at all, since deleting keys and setting TTLs are exactly the two remedies and neither is blocked. It is also why the incident reads as an application bug rather than a memory problem: the service looks half-alive and read latency is green.

allkeys vs volatile: The Trap That Breaks Writes

This is the single most common way a correctly-configured-looking Redis starts refusing writes with memory apparently to spare.

Every volatile-* policy — five of them on Redis, four on Valkey — considers only keys that have an expiry set. If no key in the dataset carries a TTL, there is nothing eligible to evict — and Redis falls back to exactly the behaviour of noeviction. The Redis key-eviction reference states it plainly: "The volatile-xxx policies behave like noeviction if no keys have an associated expiration."

So the server sits at its ceiling, holding a keyspace it is not allowed to touch, rejecting every allocating write, with a policy configured that looks like it should be preventing precisely that.

The same policy either frees memory or does nothing, depending on whether your keys carry a TTL allocating write counted memory > maxmemory? used_memory minus mem_not_counted_for_evict no write accepted yes which maxmemory-policy? allkeys-* volatile-* any key is a candidate sample the keyspace only keys with a TTL are candidates some exist evict, accept write evicted_keys climbs none OOM error evicted_keys stays at 0 identical to noeviction an allkeys policy can still fail if nothing can be freed

The diagnostic takes two commands, and the pair of numbers is unambiguous:

redis-cli CONFIG GET maxmemory-policy
redis-cli INFO stats  | grep evicted_keys
redis-cli INFO memory | grep -E 'used_memory:|maxmemory:'

evicted_keys stuck at 0 while used_memory sits at the ceiling and writes return OOM means nothing is being evicted at all. The first command tells you why: either the policy is noeviction — the default, and the likelier answer — or it is a volatile-* policy with no eligible keys. The second case is the one that surprises people, because the configuration looks right.

The fix for the volatile-* case is either to set TTLs on the cacheable portion of your keyspace, or to switch to the allkeys- equivalent. Note that evicted_keys lives in INFO stats, not INFO memory, which is why it is easy to conclude the counter does not exist.

Least Recently Modified (LRM) in Redis 8.6

Redis 8.6 added allkeys-lrm and volatile-lrm, and the distinction is a single word: LRM updates a key's recency timestamp on writes only. LRU updates it on reads and writes alike.

Timeline for one keyallkeys-lruallkeys-lrm
6 seconds untouchedidle 6sidle 6s
then a GETidle 0s — resetidle 6s — unchanged
then 4 seconds passidle 4sidle 10s
then a SETidle 0s — resetidle 0s — reset

Idle times are whole seconds and drift by about one either way; the pattern is what matters. Commands that reset the LRM timestamp include SET, INCR, APPEND, SETRANGE — and, less obviously, EXPIRE, which counts as a modification. Commands that do not include GET, STRLEN, EXISTS, TTL, TYPE and GETRANGE.

The workload it is for: data that is read constantly but written rarely, sharing an instance with data that churns. Under LRU, a configuration blob that every request reads is permanently fresh and therefore immortal, so the victim is always something else — typically session data, which goes idle between one request and the next even though it is the data actually being written. That is backwards if the blob is cheap to reload and the sessions are not. LRM changes what counts as recent: writing, not reading.

There are two hard constraints. LRM is Redis 8.6 or newer, and it is Redis only — Valkey does not have it at any version. It is also unavailable on essentially every managed Redis-compatible service, which will reject the value outright. Check the version before you plan around it, rather than trying the policy on a live instance — CONFIG SET takes effect immediately, so a failed experiment is a live configuration change:

redis-cli INFO server | grep redis_version

Why Redis Eviction Is Approximate

Redis does not maintain a global ordering of keys by recency or frequency — the bookkeeping would cost more than it saves. Instead it samples. On each eviction it inspects a small number of candidate keys, keeps the best of them in a small pool carried across successive samplings, and evicts from there. The result lands close to true LRU or LFU at a fraction of the cost. This applies to the LRU, LFU, LRM and volatile-ttl policies; the two random policies do no sampling at all and simply pick a key.

SettingDefaultWhat it controls
maxmemory-samples5Candidates inspected per eviction (1–64). Higher is more accurate and more CPU
lfu-log-factor10How quickly the LFU counter saturates as access counts grow
lfu-decay-time1Minutes before an LFU counter loses a point. 0 means never decay
maxmemory-eviction-tenacity10Time budget per eviction cycle — see below

maxmemory-eviction-tenacity is the one that is routinely misdescribed. It is not an aggressiveness dial; it is a time budget. At values up to 10 the budget is 50µs × tenacity, so the default of 10 gives each eviction cycle 500 microseconds. Between 11 and 99 it grows geometrically to roughly two minutes, and 100 removes the limit entirely.

That budget exists because eviction runs on the command path, synchronously, before the command that triggered it executes. When the budget is exhausted before memory is back under the ceiling, Redis registers a background timer to carry on. Both the inline pass and the timer run on the main thread, which makes eviction a genuine source of latency rather than a background housekeeping task — worth knowing if you are chasing p99 spikes on a memory-constrained instance.

Sizing maxmemory: What Is and Is Not Counted

Setting maxmemory to the machine's total RAM is a reliable way to get OOM-killed despite having an eviction policy configured, because the number Redis compares against the limit is not the process's full memory footprint.

Three things are excluded from the eviction threshold, reported together as mem_not_counted_for_evict in INFO memory:

  • Replica output buffer memory in excess of the replication backlog
  • The migrate client output buffer, used during atomic slot migration
  • The AOF buffer, when AOF persistence is enabled

The reasoning is that eviction should respond to data size. Counting the buffers would create a feedback loop, because the DEL commands eviction generates are themselves pushed into those buffers, making them larger.

Two things commonly claimed to be excluded are not. The replication backlog itself is counted — only replica buffer memory above it is exempt. And normal client output buffers are counted too; they are not exempt at all. A few thousand clients each holding a large pipelined response is real memory that pushes you toward the eviction threshold, and it is memory most sizing advice quietly ignores.

In practice: leave headroom below the container or machine limit rather than matching it, and when an instance is behaving unexpectedly, read the attribution rather than the total.

redis-cli INFO memory | grep -E 'used_memory:|maxmemory:|mem_not_counted_for_evict|mem_replication_backlog|mem_clients_normal|mem_aof_buffer'

Eviction in Replicas and Redis Cluster

Two behaviours here regularly surprise people.

A replica does not evict on its own. The replica-ignore-maxmemory setting defaults to yes, so a replica ignores its own maxmemory entirely and deletes keys only when the primary replicates the DEL. The intent is that a replica stays an exact copy of its primary rather than making independent decisions. The consequence is that a replica can legitimately sit above its own configured limit, and monitoring that alerts on this will page you for something working as designed.

In Redis Cluster, maxmemory is strictly per node. There is no cluster-wide memory accounting: each master compares its own usage against its own limit and evicts independently. Effective cluster capacity is maxmemory multiplied by the number of masters — and with uneven key distribution, one shard can be evicting hard while the others sit idle, producing a cache hit ratio that looks fine in aggregate and terrible for the unlucky slot range.

Which Policy Should You Choose?

The choice between LRU and LFU is a workload question covered in depth on LRU cache and LFU cache, and the difference between eviction and expiry is covered on cache eviction. For picking a value quickly:

  • A pure cache where everything is disposableallkeys-lru. The default choice, and the right one until measurement says otherwise.
  • A stable, heavily skewed hot setallkeys-lfu.
  • A mixed instance where everything cacheable carries a TTL — a volatile-* policy, provided you are confident about that TTL coverage.
  • Read-hot, write-cold data you want to keepallkeys-lrm, on Redis 8.6+.
  • A primary datastore where nothing may be droppednoeviction, plus alerting on used_memory against maxmemory, because writes will start failing rather than degrading.

Reading and Setting Eviction Policy From Java With Redisson

Redisson, a Java client for Redis and Valkey, exposes the server's configuration and memory statistics directly, so the diagnostic above does not require shell access to the box:

RedisSingle nodes = redisson.getRedisNodes(RedisNodes.SINGLE);
RedisMaster node = nodes.getInstance();

Map<String, String> memory = node.info(RedisNode.InfoSection.MEMORY);
memory.get("maxmemory_policy");              // e.g. "allkeys-lru"
memory.get("mem_not_counted_for_evict");

Map<String, String> stats = node.info(RedisNode.InfoSection.STATS);
stats.get("evicted_keys");                   // stuck at 0 = the volatile-* trap

node.setConfig("maxmemory", "4gb");                  // both settings, or neither works
node.setConfig("maxmemory-policy", "allkeys-lru");   // runtime, not persisted

The same interface offers getConfig for reading a single setting and getMemoryStatistics for the fuller MEMORY STATS breakdown. Because the limit is per node, a cluster has to be checked node by node — getRedisNodes(RedisNodes.CLUSTER) returns one RedisCluster object, and its masters come from getMasters():

RedisCluster cluster = redisson.getRedisNodes(RedisNodes.CLUSTER);
for (RedisClusterMaster master : cluster.getMasters()) {
    master.info(RedisNode.InfoSection.MEMORY).get("maxmemory_policy");
}

There is also a way around the scope constraint noted earlier. maxmemory-policy is server-wide: one policy governs every key on the instance, so if your sessions want LRU and your product catalog wants LFU, stock Redis cannot express that. Redisson sets eviction per collection instead — RMapCache takes its own maximum size and eviction mode, and a near cache takes a separate policy for its in-process tier, including SOFT and WEAK reference-based modes that have no server-side equivalent in either product. Both are in the free, open-source edition; the per-collection examples are on LRU cache and LFU cache.

Redisson PRO goes further in the same direction. Data partitioning shards one logical map across masters, so its eviction work is shared rather than concentrated on a single node, and size limits can be set in bytes instead of entry counts. Both are available on a free trial.

Redis Eviction Policy: Frequently Asked Questions

What Is the Default Redis Eviction Policy?

The default is noeviction, which evicts nothing. When memory reaches maxmemory, the write commands that would allocate are rejected with an OOM error while reads and non-allocating writes continue to work. Since maxmemory also defaults to 0 — no limit — a stock Redis server neither limits its memory nor evicts anything, and will grow until the operating system intervenes.

What Is the Difference Between allkeys-lru and volatile-lru?

The two differ only in which keys they may consider. allkeys-lru can evict any key in the keyspace; volatile-lru only considers keys that have a TTL set. Both then pick the least recently used candidate. The critical consequence is that if no key carries a TTL, volatile-lru has nothing eligible to evict and behaves exactly like noeviction, so allocating writes begin failing while memory is full of keys it will not touch.

What Happens When Redis Reaches maxmemory?

It depends entirely on maxmemory-policy. Under an eviction policy, Redis samples candidate keys and removes them until usage falls below the limit, then runs the command. Under noeviction — or under a volatile-* policy with no eligible keys — the allocating write commands are rejected with OOM command not allowed when used memory > 'maxmemory'. Reads, and non-allocating writes such as DEL and EXPIRE, succeed either way.

How Do I Know if Redis Is Evicting Keys?

Check evicted_keys in INFO stats — note that it is in the stats section, not the memory section. A rising count means eviction is working. A count stuck at zero while used_memory sits at maxmemory and writes are failing means nothing is being evicted, so read the policy with CONFIG GET maxmemory-policy: either it is noeviction, or it is a volatile-* policy on a keyspace without TTLs.

What Is allkeys-lrm and When Should I Use It?

LRM is Least Recently Modified, added in Redis 8.6. It updates a key's timestamp on writes only, so reads do not protect a key from eviction. Use it when read-hot but rarely-changed data is crowding out data that goes idle between writes — under LRU the constantly-read data is immortal, so something else is always the victim. It is Redis-only, requires 8.6 or later, and is unavailable on Valkey and on managed Redis-compatible services.

Does Valkey Support the Same Eviction Policies as Redis?

Not since Redis 8.6. Valkey supports the eight classic values — noeviction, plus the LRU, LFU and random variants in allkeys- and volatile- form, plus volatile-ttl. Redis added allkeys-lrm and volatile-lrm in 8.6, bringing it to ten. Configuration written for Redis 8.6 or later using an LRM policy will be rejected by Valkey.

Should I Set maxmemory to the Total RAM of the Server?

No — sizing maxmemory to total RAM is how an instance gets OOM-killed despite having a policy configured. Replica output buffers above the replication backlog, the migrate buffer and the AOF buffer are excluded from the eviction threshold, so Redis can exceed maxmemory in real terms. The replication backlog and normal client output buffers, by contrast, are counted. Leave headroom below the machine or container limit rather than matching it, and check mem_not_counted_for_evict in INFO memory.

Do Redis Replicas Evict Keys?

Not by default. replica-ignore-maxmemory defaults to yes, so a replica ignores its own limit and removes keys only when the primary replicates the deletion. This keeps the replica an exact copy rather than letting it make independent decisions, but it also means a replica can legitimately hold more data than its own maxmemory allows.

Similar terms