Understanding the Redis 'OOM command not allowed' Error
When Redis returns the dreaded OOM command not allowed when used memory > 'maxmemory' error, it's telling you one thing: the server has hit its configured memory limit and is refusing to execute commands that would consume more memory. This is not a bug — it's a protective mechanism. But in production, it can bring your application to a grinding halt.
The error typically manifests in application logs as:
ERR OOM command not allowed when used memory > 'maxmemory'
Or in Redis CLI when attempting writes:
127.0.0.1:6379> SET mykey "hello"
(error) OOM command not allowed when used memory > 'maxmemory'
What Triggers This Error?
Redis tracks its memory usage in real time. When the used_memory exceeds the configured maxmemory threshold, Redis blocks write commands. Read commands like GET, HGET, LRANGE, and SMEMBERS continue to work, but any command that adds new data — SET, LPUSH, SADD, HSET, ZADD, XADD — will be rejected with the OOM error.
Certain administrative commands like KEYS or FLUSHDB may also be affected if they require temporary memory allocation that pushes Redis over the limit. The exact behavior depends on the configured maxmemory-policy.
Root Cause Analysis
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →To fix this error effectively, you must understand why memory usage spiked. There are several common root causes:
1. Memory Leak from Expired Keys Not Being Evicted
Redis uses two mechanisms to expire keys: lazy expiration (checking expiration on access) and active expiration (a background process that samples keys). If you have a high volume of keys with short TTLs and low access rates, expired keys can accumulate in memory because the active expiration cycle can't keep up. This creates a "garbage" memory problem where technically expired keys still consume RAM.
# Check the number of expired keys that haven't been removed
redis-cli INFO stats | grep expired_keys
# Check memory fragmentation and overhead
redis-cli INFO memory | grep -E "used_memory_human|mem_fragmentation_ratio"
2. No Eviction Policy Configured
If maxmemory-policy is set to noeviction (the default), Redis simply refuses writes when memory is exhausted. This is the most common cause of the OOM error in production. Many teams set a maxmemory limit but forget to configure an eviction policy, leaving the default in place.
# Check current eviction policy
redis-cli CONFIG GET maxmemory-policy
# Default output if never changed:
# 1) "maxmemory-policy"
# 2) "noeviction"
3. Sudden Traffic Spike or Data Influx
A sudden burst of writes — perhaps from a new feature launch, a batch job, or a cache warming operation — can fill Redis memory faster than expected. This is especially dangerous if your maxmemory is set close to your server's physical RAM, leaving no headroom.
4. Large Key Values or Unbounded Data Structures
A single large key (a massive hash, a bloated sorted set, or a multi-megabyte string) can consume a disproportionate amount of memory. Redis memory is not linearly predictable when using complex data structures due to internal encoding overhead.
# Find the largest keys by memory usage
redis-cli --bigkeys -i 0.1
# Or use the MEMORY USAGE command on suspect keys
redis-cli MEMORY USAGE mykey
5. Client Buffer Accumulation
Client output buffers (especially for slow clients or pub/sub subscribers that can't keep up) can consume significant memory. This memory is counted in used_memory and can trigger OOM even if your dataset itself is modest.
# Check client-related memory
redis-cli CLIENT LIST | head -20
redis-cli INFO clients
6. Fork-Based Persistence Overhead
When Redis forks to write an RDB snapshot or perform an AOF rewrite, it uses copy-on-write (COW) semantics. If writes occur during the fork, memory pages are duplicated, potentially doubling memory consumption temporarily. On systems with limited RAM, this transient spike can trigger OOM.
Immediate Diagnostic Commands
When you encounter the OOM error, run these commands to assess the situation:
# 1. Get memory overview
redis-cli INFO memory
# Key metrics to watch:
# used_memory_human - Total memory used by Redis
# maxmemory_human - Configured maxmemory limit
# mem_fragmentation_ratio - Ratio of used_memory_rss / used_memory
# used_memory_dataset - Memory used purely for key-value data (excludes buffers)
# used_memory_overhead - Memory used for internal structures and buffers
# 2. Check eviction policy
redis-cli CONFIG GET maxmemory-policy
# 3. Check maxmemory setting
redis-cli CONFIG GET maxmemory
# 4. See top memory consumers by key prefix
redis-cli --scan --pattern '*' | head -100 | xargs -L1 redis-cli MEMORY USAGE
# 5. Check client buffer usage
redis-cli CLIENT LIST | awk -F' ' '{for(i=1;i<=NF;i++){if($i~/^omem=/){print $i}}}' | sort -t= -k2 -n | tail -20
How to Fix the Error
Solution 1: Set an Eviction Policy (Immediate Mitigation)
The fastest fix is to change the eviction policy from noeviction to something that automatically frees memory. Choose based on your use case:
# For pure cache use cases (most common) — evict least recently used keys
redis-cli CONFIG SET maxmemory-policy allkeys-lru
# For cache with TTL-based expiry — evict LRU among keys with expiry set
redis-cli CONFIG SET maxmemory-policy volatile-lru
# For cache where you want random eviction among expired keys
redis-cli CONFIG SET maxmemory-policy volatile-ttl
# For cache where random eviction is acceptable (faster CPU-wise)
redis-cli CONFIG SET maxmemory-policy allkeys-random
# Make it permanent by also setting in redis.conf or via CONFIG REWRITE
redis-cli CONFIG REWRITE
Important: If you use Redis for persistent data (not just caching), be extremely cautious. allkeys-lru can evict data you never intended to lose. In that case, consider volatile-lru or volatile-ttl and ensure all persistent keys do NOT have an expiry set.
Solution 2: Increase maxmemory (If Hardware Allows)
If your server has unused RAM and your data growth is legitimate, increase the limit:
# Check available system memory first
free -h
# Increase maxmemory (example: set to 4GB)
redis-cli CONFIG SET maxmemory 4294967296 # 4GB in bytes
# Or using human-readable format in redis.conf:
# maxmemory 4gb
# Verify
redis-cli CONFIG GET maxmemory
# Persist the change
redis-cli CONFIG REWRITE
Never set maxmemory to 100% of physical RAM. Leave at least 20-30% headroom for fork operations, system processes, and buffer overhead.
Solution 3: Emergency Data Cleanup
If you're in a critical situation and need immediate relief without changing configuration:
# Flush expired keys aggressively
redis-cli --scan --pattern '*' | while read key; do
redis-cli EXPIRE "$key" 1
done
# Or selectively delete keys matching a pattern (use with caution!)
redis-cli --scan --pattern 'cache:*' | xargs -L 100 redis-cli DEL
# Flush a specific database entirely (if it's purely cache)
redis-cli -n 5 FLUSHDB
# Use the DEBUG command to force eviction (Redis 4.0+)
redis-cli DEBUG SET-ACTIVE-EXPIRE-KEYS 100
Solution 4: Optimize Memory Usage
For long-term stability, reduce memory consumption structurally:
# Use shorter key names where possible
# Instead of: "user:1000:session:data:profile"
# Use: "u:1000:s:p"
# Enable compression for large values in application code
# Store compressed data in Redis with a wrapper key indicating compression
import redis
import zlib
r = redis.Redis()
data = {"large": "payload"} * 1000
compressed = zlib.compress(str(data).encode())
r.set("large_key", compressed)
# Use Redis Lists with max length to auto-truncate
# Instead of: RPUSH mylist "item"
# Use: LPUSH mylist "item" then LTRIM mylist 0 999
# Use Hash data type with small hash optimization (ziplist)
# Redis automatically uses memory-efficient encoding for small hashes
# Keep individual hashes below hash-max-ziplist-entries (default 512)
redis-cli CONFIG SET hash-max-ziplist-entries 1024
Solution 5: Tune Active Expiration
If expired keys are accumulating, tune the active expiration cycle:
# Increase the active expire cycle effort (Redis 6.0+)
# Default is 1, max is 10 — higher values use more CPU but evict faster
redis-cli CONFIG SET active-expire-effort 8
# Check current setting
redis-cli CONFIG GET active-expire-effort
Prevention: Production Best Practices
1. Always Configure maxmemory Explicitly
Never rely on the default (unlimited on 64-bit systems). Set a concrete limit based on your available RAM minus headroom:
# In redis.conf
maxmemory 6gb
maxmemory-policy allkeys-lru
# Or via CONFIG SET with CONFIG REWRITE
redis-cli CONFIG SET maxmemory 6442450944 # 6GB
redis-cli CONFIG SET maxmemory-policy allkeys-lru
redis-cli CONFIG REWRITE
2. Implement Monitoring and Alerting
Set up alerts before memory hits the limit. Monitor used_memory as a percentage of maxmemory:
# Script for memory monitoring (runs via cron or as a daemon)
#!/bin/bash
MEMORY_USED=$(redis-cli INFO memory | grep used_memory: | cut -d: -f2 | tr -d '\r')
MAXMEMORY=$(redis-cli CONFIG GET maxmemory | tail -1 | tr -d '\r')
PERCENTAGE=$((MEMORY_USED * 100 / MAXMEMORY))
if [ $PERCENTAGE -gt 80 ]; then
echo "WARNING: Redis memory usage at ${PERCENTAGE}%" | \
mail -s "Redis Memory Alert" ops@example.com
fi
3. Use INFO Command Metrics for Dashboards
Integrate these metrics into your observability stack (Prometheus, Datadog, Grafana):
# Key metrics to track continuously
redis-cli INFO memory | grep -E "used_memory_human|maxmemory_human|mem_fragmentation_ratio"
redis-cli INFO stats | grep -E "evicted_keys|expired_keys|keyspace_hits|keyspace_misses"
redis-cli INFO clients | grep -E "connected_clients|blocked_clients"
4. Set Memory Alarms in Cloud Redis Services
If using managed Redis (AWS ElastiCache, Azure Cache for Redis, GCP Memorystore):
# AWS ElastiCache: Set CloudWatch alarms on BytesUsedForCache metric
# Threshold: BytesUsedForCache > 80% of CacheBytesLimit
# Azure Cache for Redis: Alert on "Used Memory Percentage" metric
# Threshold: > 80%
# GCP Memorystore: Alert on "memory/cache/usage_ratio"
# Threshold: > 0.8
5. Implement Graceful Degradation in Application Code
Your application should handle OOM errors gracefully rather than crashing:
import redis
import time
import logging
logger = logging.getLogger(__name__)
class ResilientRedisCache:
def __init__(self, host='localhost', port=6379, db=0):
self.redis = redis.Redis(
host=host, port=port, db=db,
socket_timeout=5, socket_connect_timeout=5,
retry_on_timeout=True
)
self.fallback_cache = {}
def set_with_fallback(self, key, value, ttl=3600):
try:
self.redis.setex(key, ttl, value)
except redis.exceptions.ResponseError as e:
if 'OOM' in str(e):
logger.warning(f"Redis OOM on SET {key}, using local fallback")
# Store in local dict as last resort
self.fallback_cache[key] = {
'value': value,
'expires_at': time.time() + ttl
}
# Clean old fallback entries
self._clean_fallback()
else:
raise
def get_with_fallback(self, key):
try:
return self.redis.get(key)
except redis.exceptions.ConnectionError:
# Check fallback cache
if key in self.fallback_cache:
entry = self.fallback_cache[key]
if time.time() < entry['expires_at']:
return entry['value']
else:
del self.fallback_cache[key]
return None
def _clean_fallback(self):
now = time.time()
expired = [k for k, v in self.fallback_cache.items()
if now >= v['expires_at']]
for k in expired:
del self.fallback_cache[k]
6. Perform Regular Memory Audits
Run periodic checks to identify memory bloat before it causes problems:
# Weekly audit script
#!/bin/bash
echo "=== Redis Memory Audit $(date) ==="
redis-cli INFO memory | grep -E "used_memory_human|maxmemory_human"
echo "---"
echo "Top 10 largest keys:"
redis-cli --bigkeys -i 0.1 2>&1 | tail -20
echo "---"
echo "Keyspace distribution:"
redis-cli INFO keyspace
echo "---"
echo "Eviction stats:"
redis-cli INFO stats | grep -E "evicted_keys|expired_keys"
7. Configure Client Output Buffer Limits
Prevent runaway client buffers from consuming all memory:
# In redis.conf or via CONFIG SET
# Format: class hard-limit soft-limit soft-seconds
# Limit normal clients to 256MB hard, 128MB soft for 60 seconds
redis-cli CONFIG SET client-output-buffer-limit "normal 268435456 134217728 60"
# Limit slave/replica clients (for replication)
redis-cli CONFIG SET client-output-buffer-limit "replica 536870912 268435456 60"
# Limit pub/sub clients aggressively
redis-cli CONFIG SET client-output-buffer-limit "pubsub 67108864 33554432 30"
8. Use Dedicated Cache Instances
Isolate cache data from persistent data by using separate Redis instances or logical databases. This allows you to apply aggressive eviction policies (allkeys-lru) on cache instances while keeping noeviction or volatile-lru on persistent stores.
# Example: Use DB 0 for persistent data, DB 1 for cache
# In application code:
persistent_redis = redis.Redis(db=0)
cache_redis = redis.Redis(db=1)
# Configure per-database eviction (Redis 6.0+ with ACLs)
# Create a restricted user for cache operations
redis-cli ACL SETUSER cacheuser on >password ~cache:* +set +get +del
redis-cli ACL SETUSER cacheuser -@all +set +get +del ~cache:*
Understanding Eviction Policies in Depth
Choosing the right eviction policy is critical. Here's what each policy actually does:
# Policy Behavior
# noeviction Reject writes, return OOM error (default)
# allkeys-lru Evict any key using approximate LRU algorithm
# volatile-lru Evict only keys with TTL, using approximate LRU
# allkeys-random Evict random keys from entire keyspace
# volatile-random Evict random keys among those with TTL
# volatile-ttl Evict keys with shortest TTL remaining
# allkeys-lfu (4.0+) Evict any key using approximate LFU algorithm
# volatile-lfu (4.0+) Evict keys with TTL using approximate LFU
LRU (Least Recently Used) is ideal for most caching scenarios. LFU (Least Frequently Used) is better when you want to protect frequently-accessed keys from eviction even if they haven't been accessed recently. volatile-ttl is excellent when all keys have well-distributed TTLs and you want the closest-to-expiry keys evicted first.
Handling Fork Memory Spikes During Persistence
When Redis forks for RDB saves or AOF rewrites, memory can temporarily double. Here's how to prevent OOM during these operations:
# 1. Disable transparent huge pages (THP) — critical for Redis
# Run at system level:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
# Persist across reboots by adding to /etc/rc.local or grub config
# 2. Set vm.overcommit_memory=1 to allow memory overcommit for fork
sysctl vm.overcommit_memory=1
# 3. Schedule persistence during low-write periods
# For example, run backups at 3 AM when traffic is minimal
# 4. Use AOF with appendfsync everysec instead of always
redis-cli CONFIG SET appendfsync everysec
# 5. Consider using Redis 7.0+ which has improved fork COW handling
# and supports multiple RDB snapshots without fork in some configurations
Conclusion
The Redis OOM error is fundamentally a configuration and capacity planning issue. It occurs when memory consumption exceeds the defined limit and no eviction policy is in place to automatically free space. The root cause is almost always one of three things: the default noeviction policy being left unchanged, a legitimate traffic spike that outpaced capacity, or accumulated memory overhead from expired keys, client buffers, or fork operations.
To fix it immediately, configure a sensible eviction policy like allkeys-lru for cache workloads or volatile-lru for mixed persistent/cache data. For long-term prevention, implement memory monitoring with alerts at 80% utilization, perform regular memory audits, set explicit client buffer limits, and always leave 20-30% RAM headroom above your maxmemory setting. Your application code should also handle OOM errors gracefully with fallback mechanisms rather than crashing. With these practices in place, you'll transform the OOM error from a production emergency into a well-managed operational signal.