Redis 'OOM command not allowed': Why Switching to LRU Can Cost You Data
The first sign is almost never a Redis alert. It is your application. Background jobs stop acknowledging, a queue quietly stops draining, or a checkout write throws an exception that gets swallowed three layers up. You SSH into the box, everything looks healthy, plenty of CPU, and then you run one command and Redis tells you the truth.
OOM command not allowed when used memory > 'maxmemory'
The obvious fix is one line, and it is exactly the line that turns a cache outage into a data-loss incident. Redis will happily accept writes again the moment you switch its eviction policy, because it starts deleting your existing keys to make room. If those keys were only cached copies of data you can rebuild, fine. If any of them were a job in a queue, a distributed lock, or the only copy of something, you just traded a loud, safe failure for a quiet, dangerous one.
This error means Redis hit maxmemory while running the default noeviction policy, so it rejects writes instead of deleting data. Do not blindly switch to allkeys-lru. First decide what is on that instance.
- If it is a pure cache (everything is reconstructable from a database), set
maxmemory-policy allkeys-lruand give it a sensiblemaxmemory. - If it holds queues, locks, sessions or any primary data, do not enable eviction. Raise
maxmemory, add TTLs to the throwaway keys, or split cache and durable data onto separate instances. - Check the pressure with
redis-cli info memoryand watchevicted_keysover time, not just at the moment it breaks.
The exact error and where it comes from
The full message Redis returns to the client is:
(error) OOM command not allowed when used memory > 'maxmemory'.
It is returned for any command that would increase memory usage: SET, LPUSH, HSET, SADD, INCR on a new key, and so on. Read commands still work. That is why the box looks healthy from the outside: GET succeeds, PING succeeds, monitoring that only checks liveness sees nothing wrong. It is writes, specifically, that are being refused.
Confirm it is really the memory ceiling and not something else:
redis-cli info memory | grep -E 'used_memory:|used_memory_human|maxmemory:|maxmemory_human|maxmemory_policy'
If used_memory is at or just under maxmemory and maxmemory_policy is noeviction, you have found it.
Why the default policy refuses writes instead of cleaning up
Redis ships with maxmemory-policy noeviction by default, and that default is correct, even though it is the thing that just broke your night. noeviction means: when I am full, I will not delete any of your data to make room, I will refuse the write and tell you loudly. That is the only safe default for a data store, because Redis cannot know whether the key you are asking it to evict is a disposable cache entry or the only record that a customer paid you.
The trap is that Redis is marketed and used as a cache, so people assume it behaves like one out of the box. A cache is supposed to forget. But an out-of-the-box Redis is not a cache, it is a fast in-memory data store that you can configure to behave like a cache. The eviction policy is the switch, and flipping it changes Redis from "I will protect your data and complain when I am full" to "I will silently delete your data to keep accepting writes."
The dangerous fix everyone copies
Search this error and the top answer is almost always the same single command:
# The advice you will find everywhere. Read the rest before you run it.
redis-cli config set maxmemory-policy allkeys-lru
allkeys-lru means "evict the least recently used key from any key in the database when full." The word allkeys is the danger. It does not care whether a key has a TTL, whether it is a cache entry, or whether it is the head of a job queue. Under memory pressure it will pick the oldest-touched key and delete it. A queue entry that has been sitting there waiting to be processed is, by definition, not recently used. It is a prime eviction candidate. So is a distributed lock that a long-running job is holding. So is a user session that happens to be idle.
The failure is silent. No error is returned. The write succeeds. The job simply vanishes from the queue and is never processed, or a lock disappears and two workers run the same critical section at once. You will not find out from Redis. You will find out from a customer.
The same applies to allkeys-lfu (least frequently used) and allkeys-random. All three are safe only if every single key on the instance is reconstructable from a persistent source.
The fix, decided by what the instance actually holds
Step 1: Find out what is on the instance
Before touching the policy, learn what you would be authorising Redis to delete. Sample the keyspace:
redis-cli info keyspace
redis-cli --scan --count 100 | head -50
Look at the key name prefixes. If you see celery, bull:, rq:, queue:, lock:, sess: or anything that looks like durable state next to your cache keys, you have a mixed instance and eviction is not safe as a blanket setting.
Step 2 (pure cache): set a policy and a real ceiling
If and only if everything is a rebuildable cache, enable eviction and pin a memory ceiling so the OS never has to kill Redis:
redis-cli config set maxmemory 2gb
redis-cli config set maxmemory-policy allkeys-lru
Then make it survive a restart by putting it in redis.conf:
maxmemory 2gb
maxmemory-policy allkeys-lru
Prefer volatile-lru over allkeys-lru if you set TTLs on your cache keys but keep a handful of un-expiring keys you never want evicted. volatile-* policies only ever evict keys that have a TTL set, which gives you a way to mark certain keys as off-limits by simply not giving them an expiry.
Step 3 (mixed or durable): do not evict, relieve the pressure instead
If the instance holds anything you cannot afford to lose, keep noeviction and attack the memory usage directly:
- Raise
maxmemoryif the machine has spare RAM. On a VPS, check withfree -mfirst; leave headroom for the OS and for Redis fork overhead duringBGSAVE. Do not setmaxmemoryto the full machine RAM. - Add TTLs to the disposable keys. Most OOM situations on a mixed instance are caused by cache keys that were written with no expiry and accumulated forever. Find them and set
EXPIREon write from now on. - Split the workloads. The real fix is two Redis instances (or two logical databases with separate memory budgets is not enough, since
maxmemoryis per-instance): one withallkeys-lrufor the cache, one withnoevictionfor queues and locks. This is the setup you want to reach eventually, and it is cheap insurance.
If your stale-cache problem is the deeper issue, the companion piece on fixing Redis cache invalidation properly covers TTL and key-design choices that keep memory bounded in the first place.
Verification: prove writes work and nothing is being silently eaten
After the change, confirm two things. First, that writes succeed again:
redis-cli set cwk:healthcheck ok
# expected: OK
Second, and more important on a cache instance, confirm eviction is happening at a rate you understand rather than deleting your working set constantly:
redis-cli info stats | grep -E 'evicted_keys|expired_keys|keyspace_hits|keyspace_misses'
A steadily climbing evicted_keys with a collapsing hit rate means your maxmemory is too small for the working set, and allkeys-lru is thrashing: it evicts a key, then something immediately asks for it again. That is a sizing problem, not a policy problem. Raise maxmemory or shrink what you store.
Catch it before writes fail next time
The whole incident is preventable with one alert. Redis exposes everything you need in info memory and info stats. Alert on the ratio of used_memory to maxmemory crossing roughly 85 percent, and separately alert on any non-zero rate of evicted_keys on an instance that is supposed to be noeviction durable state (it should always be zero there; if it is not, someone changed the policy). If you run Redis next to a database on a small box, also keep an eye on the host, because a Redis that grows without a maxmemory set will eventually be chosen by the kernel's out-of-memory killer, which is a far messier failure than the clean OOM error.
When it is still broken
If you raised maxmemory and writes still fail immediately, the box is genuinely out of RAM at the OS level and Redis cannot allocate. Check free -m and dmesg | tail for the OOM killer. If used_memory keeps climbing with no matching growth in your data, look for a memory leak pattern: a key being LPUSHed to without any consumer draining it, or client output buffers backing up (info clients, client_recent_max_output_buffer). If used_memory_rss is far larger than used_memory, you are looking at fragmentation, and activedefrag yes may help, but treat that as a separate investigation, not part of this fix.
Frequently asked questions
- What does 'OOM command not allowed when used memory > maxmemory' actually mean?
- Redis has reached its configured maxmemory limit and its eviction policy is noeviction, the default. Instead of deleting keys to make room, Redis refuses every command that would use more memory and returns this error. Read commands still work, which is why the server can look healthy while all writes fail.
- Is it safe to just set maxmemory-policy to allkeys-lru?
- Only if every key on that Redis instance is a cache entry you can rebuild from a database. allkeys-lru deletes the least recently used key of any kind when memory is full, including queue jobs, distributed locks and sessions, with no error. If the instance holds any durable data, raise maxmemory or split cache and durable workloads onto separate instances instead.
- How do I raise the Redis memory limit without restarting?
- Run redis-cli config set maxmemory 2gb to change it live, then also add maxmemory 2gb to redis.conf so it survives a restart. Check free -m first and leave headroom for the OS and for the fork Redis creates during background saves. Never set maxmemory to the full machine RAM.
- How do I monitor for this before writes start failing?
- Alert when used_memory divided by maxmemory crosses about 85 percent, using redis-cli info memory. Separately, watch evicted_keys in info stats: on a durable noeviction instance it should always be zero, and any non-zero value means someone changed the policy and data is being deleted silently.