Troubleshooting
Redis & caching

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.

When Redis refuses writesKeys accumulateno TTL, resultsmaxmemory reachedlimit hitPolicy noevictionnothing to evictWrites rejectedOOM errorA cache should evict; a Celery broker should not. One instance cannot do both well.
  1. Check memory and policy: redis-cli INFO memory and redis-cli CONFIG GET maxmemory-policy.
  2. Find the largest keys and key patterns: redis-cli --bigkeys and a sample of key prefixes.
  3. For a cache-only instance, use maxmemory-policy allkeys-lru (or allkeys-lfu) so old entries are evicted.
  4. For a Celery broker, keep noeviction, make sure consumers keep up, and store results with an expiry (or not at all).
  5. Give every cache entry a timeout; avoid timeout=None for data that can grow without bound.
  6. Split cache and broker into separate Redis instances with different policies, and size maxmemory with 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
memory full and nothing to evictused_memory_human:1.00Gmaxmemory_human:1.00Gmaxmemory_policy:noeviction1231At the limit.2The configured ceiling.3So writes are refused: OOM command not allowed.Right for a broker, wrong for a cachedjango-deployment.com
redis-cli INFO memory and CONFIG GET maxmemory-policy show these.
  • maxmemory_policy:noeviction with used_memory at maxmemory explains 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 like noeviction.
  • INFO keyspace shows keys per database; Django cache and Celery often live in different DB numbers on the same instance, but maxmemory and 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 patternSourceUsual fix
celery-task-meta-*Celery result backendresult_expires, or ignore_result=True
celery, unacked, queue namesqueued tasks piling upmore workers, fix failing consumers
:1:views.decorators.cache.*per-view cache entriesshorter timeouts, fewer variations
:1:django.contrib.sessions.cache*cache-backed sessionsSESSION_COOKIE_AGE, cached_db sessions
app-specific prefixes without TTLcache.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
pick the policy by roleWhen fullSuitsnoevictionrefuses writes✓broker, queuesallkeys-lruevicts least recently used✓cacheallkeys-lfuevicts least frequently usedcache with hot keysvolatile-lruevicts keys with a TTL only!mixed use: riskyA cache should never be configured with noevictiondjango-deployment.com
CONFIG SET applies immediately; also write it to redis.conf.

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
a growing queue fills the brokerWorkers stopcrash or deployTasks keep arrivingqueue growsmaxmemory reachednoevictionEnqueue failsOOM command not allowedFixrestore workers, then size memoryAlert on queue length long before memory runs outdjango-deployment.com
Evicting broker data would silently drop tasks, which is worse.

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
one Redis cannot have two policiesShared instance✕cache fills memory✕noeviction blocks the cache✕LRU would delete queued tasksSeparate instances✓cache: allkeys-lru✓broker: noeviction, AOF✓each sized for its jobSeparate databases (/0, /1) share one memory limit and one policydjango-deployment.com
Two small instances are cheaper than an outage.

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

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.

Keep going