If Redis is only a cache, run redis-cli CONFIG SET maxmemory-policy allkeys-lru and put the same setting in redis.conf (or your managed service's parameter group): writes go through again, because Redis now evicts least recently used keys instead of refusing new ones. If the instance also holds queues, sessions or locks, keep volatile-lru and write every cache key with a TTL (SET key value EX 3600), because the volatile policies can only evict keys that have one.
This story was written with the assistance of an AI writing program. I reproduced the error on Redis 8.10.2 with redis-py 8.1.0 and Python 3.14.4, with maxmemory at 20 MB and 1 KiB values so the instance fills in seconds. Every number below comes from those runs.
Under the default policy, noeviction, the 15,201st SET failed. redis-py raised redis.exceptions.OutOfMemoryError: command not allowed when used memory > 'maxmemory'.; redis-cli prints the same text after OOM. evicted_keys was 0.
Not every command failed. GET, EXISTS, EXPIRE and DEL still worked; SET, INCR and LPUSH were refused. Redis rejects commands that can grow memory and lets reads and deletes through, so the outage looks partial: cache reads keep hitting while writes, counters and job enqueues fail. Deleting 1,000 keys let the next SET in.
Batches split too. A MULTI/ EXEC with EXPIRE, SET and INCR was rejected whole, EXPIRE included. The same three commands in a plain pipeline applied the EXPIRE and failed the other two.
Self-hosted Redis defaults to noeviction; managed services often default to volatile-lru. I switched to volatile-lru and refilled with keys that had no TTL. The 15,200th SET failed with the same error and nothing was evicted: the volatile policies only choose among keys with an expiry, and there were none. Four commands show it:
$ redis-cli CONFIG GET maxmemory-policy
maxmemory-policy
volatile-lru
$ redis-cli INFO keyspace
db0:keys=15199,expires=0,avg_ttl=0,subexpiry=0
$ redis-cli INFO stats | grep evicted_keys
evicted_keys:0
$ redis-cli SET probe 1
OOM command not allowed when used memory > 'maxmemory'.
expires=0 next to thousands of keys means the policy has nothing to evict. With ex=3600 on every write, the same instance accepted 60,000 of 60,000 writes, evicted 45,155 keys and kept 14,845.
Under allkeys-lru with no TTLs, all 60,000 writes succeeded and 44,863 keys were evicted. None of the first 1,000 keys written were left; 982 of the last 1,000 were. For a pure cache, that is the behavior you want.
Then I put a 3,000-item list named jobs on the instance before the cache writes, the way small apps share one Redis between a task queue and a cache. Under allkeys-lru every cache write succeeded and the list was gone: EXISTS jobs returned 0, and nothing raised an error. Under volatile-lru with a TTL on each cache key, all 60,000 cache writes succeeded and the list kept its 3,000 items. Under noeviction the list survived, but cache writes failed after 12,489.
allkeys-lru belongs on an instance that holds nothing you cannot recompute. Anything else gets volatile-lru plus TTLs, or better, its own instance.
This is the write path I tested:
import redis
r = redis.Redis(host="localhost", port=6379)
def cache_set(key, value, ttl=3600):
try:
r.set(key, value, ex=ttl) # a TTL makes the key evictable under volatile-lru
except redis.exceptions.OutOfMemoryError:
return False # full and nothing evictable: skip the cache, keep serving
return True
I ran it unchanged against a local Redis on port 6379. On a fresh volatile-lru instance it returned True 60,000 times out of 60,000 while Redis evicted 45,135 keys. On a full instance whose 15,199 existing keys had no TTL, it returned False 2,000 times out of 2,000: requests were still served, but nothing new was cached. The except keeps the app up, so count those False returns and alert on them.
On a full noeviction instance, CONFIG SET maxmemory-policy allkeys-lru took effect at once: the next 2,000 cache writes succeeded, with 2,051 evictions. A CONFIG SET is lost on restart, so persist the change in the config too.
If the instance has to stay on volatile-lru, you can add TTLs while it is full, because EXPIRE is not refused. I set a 600-second TTL on 3,000 keys that had none; all 3,000 calls succeeded, and the next 2,000 writes went through with 2,092 evictions. In production that is a SCAN over the cache key pattern with an EXPIRE per match.
With 12,000 keys, used_memory was 16,744,312 bytes, about 16 of the 20 MB. One 8 MB SET was still refused under noeviction, and used_memory was below the limit before and after. My reading is that the 8 MB argument is already in Redis's memory when it checks the limit, so the request counts against itself. Under allkeys-lru the same write succeeded after 4,794 evictions, and used_memory read 22,416,912 bytes right after, above the 20,971,520 limit. maxmemory is a target Redis evicts toward, not a hard cap, so leave headroom below the container or VM limit.
CONFIG GET maxmemory-policy and INFO keyspace first. noeviction, or a volatile-* policy with expires=0, means Redis has nothing it may evict. allkeys-lru, persisted in config, not only set at runtime. volatile-lru and a TTL on every cache write, or separate instances.maxmemory or less data, not a policy.OutOfMemoryError on cache writes and alert on it; reads keep working, so the outage looks partial.MULTI when writes must land together; a plain pipeline can apply half of them.