Fix Redis "OOM command not allowed" in Django
Fix Redis OOM command not allowed when used memory exceeds maxmemory in Django: find what fills Redis, choose the right eviction policy for cache versus Celery, set TTLs, and split instances.
Problem statement
Django requests, Celery task submissions, or cache writes fail with:
redis.exceptions.ResponseError: OOM command not allowed when used memory > 'maxmemory'.
Redis has reached its configured memory limit (maxmemory), and its eviction policy does not allow it to free space by removing keys, so it rejects writes. Reads still work, which is why the site may partly function while task queueing, sessions, and caching fail.
The right fix depends on what Redis is used for. A cache should evict old keys; a Celery broker must never evict queued tasks. Many deployments use one Redis for both, which is the root of the problem. This guide finds what fills the memory and configures each role correctly.
- Check memory and policy:
redis-cli INFO memoryandredis-cli CONFIG GET maxmemory-policy. - Find the largest keys and key patterns:
redis-cli --bigkeysand a sample of key prefixes. - For a cache-only instance, use
maxmemory-policy allkeys-lru(orallkeys-lfu) so old entries are evicted. - For a Celery broker, keep
noeviction, make sure consumers keep up, and store results with an expiry (or not at all). - Give every cache entry a timeout; avoid
timeout=Nonefor data that can grow without bound. - Split cache and broker into separate Redis instances with different policies, and size
maxmemorywith headroom.
Step-by-step solution
1. Inspect memory and policy
redis-cli -u "$REDIS_URL" INFO memory | grep -E "used_memory_human|maxmemory_human|maxmemory_policy|mem_fragmentation_ratio"
redis-cli -u "$REDIS_URL" INFO keyspace
redis-cli -u "$REDIS_URL" INFO stats | grep evicted_keys
maxmemory_policy:noevictionwithused_memoryatmaxmemoryexplains the error.volatile-lru(a default on some managed services) only evicts keys with a TTL. If most keys have no expiry, it cannot free space and behaves likenoeviction.INFO keyspaceshows keys per database; Django cache and Celery often live in different DB numbers on the same instance, butmaxmemoryand the policy apply to the whole instance.
2. Find what uses the memory
redis-cli -u "$REDIS_URL" --bigkeys
redis-cli -u "$REDIS_URL" -n 0 --scan --count 1000 | head -2000 | sed -E 's/[:.][^:.]*$//' | sort | uniq -c | sort -rn | head
The second command samples key names and groups them by prefix. Typical findings in Django projects:
| Key pattern | Source | Usual fix |
|---|---|---|
celery-task-meta-* | Celery result backend | result_expires, or ignore_result=True |
celery, unacked, queue names | queued tasks piling up | more workers, fix failing consumers |
:1:views.decorators.cache.* | per-view cache entries | shorter timeouts, fewer variations |
:1:django.contrib.sessions.cache* | cache-backed sessions | SESSION_COOKIE_AGE, cached_db sessions |
| app-specific prefixes without TTL | cache.set(key, value, None) | set a timeout |
Check a suspicious key's size and TTL:
redis-cli -u "$REDIS_URL" MEMORY USAGE "celery-task-meta-0b1c..."
redis-cli -u "$REDIS_URL" TTL "celery-task-meta-0b1c..."
A TTL of -1 means the key never expires.
3. Fix a cache instance
Configure the cache Redis to evict:
# redis.conf (cache instance)
maxmemory 1gb
maxmemory-policy allkeys-lru
or at runtime (persist it in the config file too):
redis-cli -u "$REDIS_URL" CONFIG SET maxmemory-policy allkeys-lru
On managed services (ElastiCache, Azure Cache, Memorystore), change the policy through a parameter group or configuration setting.
In Django, give entries a timeout:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": os.environ["REDIS_CACHE_URL"],
"TIMEOUT": 300,
}
}
TIMEOUT is the default for cache.set() calls without an explicit timeout. Audit code for cache.set(key, value, None) or timeout=None, which store forever.
If sessions use the cache backend, eviction logs users out. Use cached_db sessions, which fall back to the database, or put sessions in their own Redis with noeviction and a bounded SESSION_COOKIE_AGE.
4. Fix a Celery broker instance
A broker must not evict: losing queued messages means losing tasks. Keep noeviction and address why memory grows:
- Queues backing up: workers are down, too slow, or failing. Check queue lengths (
redis-cli LLEN celery) and worker health. Scale workers or fix the failing task. - Results piling up: every task stores a result for a day by default. Reduce or disable:
CELERY_RESULT_EXPIRES = 3600 # seconds
CELERY_TASK_IGNORE_RESULT = True # if you never read results
Per task: @app.task(ignore_result=True). Results you do need can live in the database instead of Redis.
- Large task arguments: passing big payloads (file contents, large lists) through the broker. Pass IDs and load data inside the task.
5. Separate the roles
Run two Redis instances (or two managed clusters): one for the cache with allkeys-lru, one for the Celery broker (and results) with noeviction. They can run on the same host with different ports and maxmemory values:
CACHES["default"]["LOCATION"] = "redis://10.0.0.8:6379/0" # cache, allkeys-lru
CELERY_BROKER_URL = "redis://10.0.0.8:6380/0" # broker, noeviction
Size maxmemory below the machine's memory with headroom for fragmentation and, if persistence is enabled, for the fork during background saves.
Verification check:
redis-cli -u "$REDIS_CACHE_URL" CONFIG GET maxmemory-policy
redis-cli -u "$CELERY_BROKER_URL" CONFIG GET maxmemory-policy
redis-cli -u "$CELERY_BROKER_URL" --scan --pattern "celery-task-meta-*" | wc -l
redis-cli -u "$REDIS_CACHE_URL" INFO memory | grep used_memory_human
Expect allkeys-lru (or allkeys-lfu) on the cache, noeviction on the broker, a bounded number of result keys, and cache memory stabilising below maxmemory with evicted_keys increasing slowly.
Explanation
maxmemory caps the memory Redis uses for data. When a write would exceed it, Redis applies maxmemory-policy: evict keys by some rule, or refuse the write (noeviction). Caches are designed to lose data, so eviction is correct there. Brokers and session stores are not, so for them the fix is keeping data bounded (expiring results, processing queues) and giving them enough memory. Sharing one instance forces a single policy onto both kinds of data, which is why OOM errors are common in combined setups.
Edge cases / notes
Fragmentation
mem_fragmentation_ratio well above 1.5 means Redis holds more memory from the operating system than its data needs. Active defragmentation (activedefrag yes, available in Redis 4 and newer with jemalloc) or a restart reclaims it.
Memory limits in containers
If Redis runs in a container with a memory limit but maxmemory is unset or larger, the container is OOM-killed instead of Redis rejecting writes. Set maxmemory below the container limit.
When to automate this
Alert when used_memory exceeds 80 percent of maxmemory, when broker queue length grows steadily, and when celery-task-meta-* keys keep increasing. Keep Redis configuration (policy, memory, persistence) in version-controlled config rather than one-off CONFIG SET commands.
Next steps
- Set up the cache correctly with Set Up Redis Caching for Django in Production.
- Review all Redis settings with the Redis Production Checklist for Django.
- Protect queued tasks with Configure Redis Persistence for Django and Celery.
- If Redis becomes unreachable instead, see Fix Redis Connection Refused in Django.
FAQ
What does "OOM command not allowed when used memory > maxmemory" mean?
Redis reached its memory limit and its eviction policy does not allow freeing space, so it rejects write commands.
Which maxmemory-policy should a Django cache use?
allkeys-lru or allkeys-lfu, so the least useful entries are evicted when memory is full.
Can I use allkeys-lru on a Redis instance that is also the Celery broker?
No. Eviction could delete queued tasks. Use separate instances, with noeviction for the broker.
Why is Redis full of celery-task-meta keys?
Celery stores task results in Redis for a day by default. Lower result_expires or ignore results you never read.