← Back to DevBytes

Fix Redis 'OOM command not allowed' Error

Understanding the Redis 'OOM command not allowed' Error

When working with Redis, encountering the dreaded OOM command not allowed error can bring your application to a grinding halt. This error is Redis's way of telling you that it has exhausted its allocated memory and cannot process write operations. Let's explore exactly what this means, why it occurs, and how to fix it definitively.

What Causes This Error?

Redis operates primarily in memory, with optional disk persistence. Every key you store—strings, hashes, lists, sets, sorted sets, streams—consumes RAM. Redis has a configuration directive called maxmemory that caps the total memory Redis can use. When Redis hits this limit, it refuses write commands (SET, HSET, LPUSH, SADD, ZADD, etc.) and returns the error:

OOM command not allowed when used memory > 'maxmemory'

This is a protective mechanism. Without it, Redis would consume all available system memory, potentially triggering the operating system's OOM killer and crashing the Redis process—or worse, destabilizing the entire server.

The error only affects write operations. Read operations like GET, HGET, LLEN, SMEMBERS, and similar commands continue to work normally, which is why your application might partially function while mysteriously failing on writes.

Why It Matters

An OOM error in Redis rarely stays contained. Its effects ripple through your application stack:

Understanding the severity of this error is crucial. It's not a minor hiccup—it's a data integrity and availability crisis that demands immediate attention.

Diagnosing the Error

Before applying fixes, confirm that memory exhaustion is actually the problem. Connect to Redis via redis-cli or your client library and run these diagnostic commands:

# Check current memory usage and limit
redis-cli INFO memory

# Key metrics to look for:
# used_memory_human: Actual memory consumed (e.g., "2.45G")
# maxmemory_human: Configured limit (e.g., "2.00G")
# maxmemory: Exact limit in bytes
# mem_allocator: Usually jemalloc or libc

# Check eviction policy
redis-cli CONFIG GET maxmemory-policy

# See total keys count
redis-cli DBSIZE

# Analyze memory by key pattern (requires redis-cli --bigkeys)
redis-cli --bigkeys

If used_memory exceeds maxmemory, you've confirmed the root cause. The --bigkeys flag scans all keys and reports the largest ones by type, helping you identify memory hogs.

You can also monitor in real-time using:

# Watch memory stats continuously (every 2 seconds)
redis-cli --stat

# Or programmatically
redis-cli INFO stats | grep -E "evicted_keys|expired_keys"

Fix 1: Increase Redis Max Memory

The most direct fix is giving Redis more RAM. This works well if your server has spare memory and your data growth is legitimate rather than caused by leaks or inefficiencies.

Option A: Runtime change (immediate, non-persistent)

# Set maxmemory to 4GB in redis-cli
redis-cli CONFIG SET maxmemory 4294967296

# Or using human-readable format (Redis 4.0+)
redis-cli CONFIG SET maxmemory 4gb

# Verify the change took effect
redis-cli CONFIG GET maxmemory

Option B: Permanent change via configuration file

Edit redis.conf (typically at /etc/redis/redis.conf or /etc/redis/6379.conf):

# Find and modify the maxmemory line
maxmemory 4gb

# Or uncomment and set if not present
# maxmemory <bytes>

Then restart Redis for the change to take effect:

# On systemd-based systems
sudo systemctl restart redis

# Or directly
sudo service redis-server restart

# Verify after restart
redis-cli CONFIG GET maxmemory
# Should return: "4gb"

Option C: Persistent runtime change

# Set at runtime AND persist to config file
redis-cli CONFIG SET maxmemory 4gb
redis-cli CONFIG REWRITE

# CONFIG REWRITE updates redis.conf with current runtime settings
# This survives restarts without manual file editing

Important caveat: Increasing memory only postpones the problem if your data keeps growing unbounded. Always pair this fix with monitoring and an eviction strategy.

Fix 2: Change Eviction Policy

Redis supports several eviction policies that determine which keys to remove when memory is full. The default policy in many Redis distributions is noeviction, which is exactly what causes the OOM error—it simply refuses writes.

Check your current policy:

redis-cli CONFIG GET maxmemory-policy
# Default often returns: "noeviction"

Here are all available eviction policies and when to use each:

# Eviction policies overview:
# noeviction         - Don't evict, return error on writes (default, causes OOM)
# allkeys-lru        - Evict any key using LRU algorithm, regardless of expiry
# volatile-lru       - Evict only keys with expiry set, using LRU
# allkeys-random     - Evict random keys across entire keyspace
# volatile-random    - Evict random keys among those with expiry set
# volatile-ttl       - Evict keys with expiry set, preferring shorter TTL
# allkeys-lfu        - Evict any key using LFU (Least Frequently Used) - Redis 4.0+
# volatile-lfu       - Evict keys with expiry using LFU - Redis 4.0+

For caching use cases, allkeys-lru or allkeys-lfu is usually best:

# Set allkeys-lru (evicts least recently used keys across all keys)
redis-cli CONFIG SET maxmemory-policy allkeys-lru
redis-cli CONFIG REWRITE  # persist it

For mixed workloads where some data must never be evicted, use volatile policies:

# Only evict keys that have an expiration set
redis-cli CONFIG SET maxmemory-policy volatile-lru
redis-cli CONFIG REWRITE

Critical distinction: With volatile policies, keys without an expiry (TTL) are never evicted. If you store persistent data without setting EXPIRE, volatile-lru won't help—Redis will still hit OOM. In that case, explicitly set TTLs on cacheable data:

# Always set expiry on cache entries
SET user:123:profile "{...}" EX 3600
SET session:abc123 "..." EX 86400

# Or update expiry on existing keys
EXPIRE user:123:profile 3600

For session stores or API rate limiters, volatile-ttl works well because it removes keys closest to natural expiration first, minimizing premature eviction.

Fix 3: Optimize Memory Usage

Sometimes the best fix is reducing memory consumption. Redis offers several optimization techniques that can dramatically shrink your memory footprint without losing data.

Use shorter key names:

# Verbose keys - each character costs bytes
SET "user:profile:by-id:12345:full-data:v2" "..."
SET "user:profile:by-id:12346:full-data:v2" "..."

# Compact keys - same information, less memory
SET "u:12345:p" "..."
SET "u:12346:p" "..."

# For 1 million keys, shortening by 20 chars saves ~20MB

Use Redis data types efficiently:

# Instead of multiple string keys for related data:
SET user:123:name "Alice"
SET user:123:email "alice@example.com"
SET user:123:age "30"

# Use a hash - significantly less memory for many fields
HSET user:123 name "Alice" email "alice@example.com" age 30

# For small hashes, Redis uses ziplist encoding which is extremely compact
# Memory savings can be 80%+ for many small records

Enable compression-friendly encodings:

# Check current encoding of a key
redis-cli OBJECT ENCODING user:123

# Redis automatically uses memory-efficient encodings for small data:
# - ziplist for small lists, hashes, sorted sets
# - intset for small integer sets
# - embstr for short strings

# Tune thresholds to keep data in compact encodings longer
redis-cli CONFIG SET hash-max-ziplist-entries 1024
redis-cli CONFIG SET hash-max-ziplist-value 128
redis-cli CONFIG SET list-max-ziplist-size -2  # up to 8KB per list node
redis-cli CONFIG SET zset-max-ziplist-entries 1024
redis-cli CONFIG SET zset-max-ziplist-value 128

# These settings keep more data in compact ziplist format
# Trade-off: slightly more CPU for much less memory

Aggressively set TTLs:

# Audit keys without expiration
# Find keys with no TTL using Lua script
redis-cli --eval check-no-ttl.lua

# Example: set expiry on all keys matching a pattern
redis-cli --scan --pattern "cache:*" | while read key; do
  redis-cli EXPIRE "$key" 3600
done

Use Redis 7.0+ memory-efficient features:

# Redis 7.0 introduced memory-efficient hash storage
# Check your Redis version
redis-cli INFO server | grep redis_version

# If 7.0+, use the new listpack encoding (successor to ziplist)
redis-cli CONFIG SET hash-max-listpack-entries 1024
redis-cli CONFIG SET hash-max-listpack-value 128

Fix 4: Implement Application-Level Strategies

Beyond Redis configuration, application-level changes can prevent OOM errors entirely.

Implement a write-with-fallback pattern:

// Node.js Redis client with OOM fallback
const redis = require('redis');
const client = redis.createClient();

async function safeSet(key, value, ttl = 3600) {
  try {
    await client.set(key, value, { EX: ttl });
    return true;
  } catch (err) {
    if (err.message.includes('OOM')) {
      // Fallback 1: Evict oldest keys manually
      const randomKey = await client.randomKey();
      if (randomKey) await client.del(randomKey);
      
      // Fallback 2: Retry the write
      try {
        await client.set(key, value, { EX: ttl });
        return true;
      } catch (retryErr) {
        // Fallback 3: Log and alert
        console.error('Redis OOM - write failed definitively', retryErr);
        // Optionally write to disk cache or database
        return false;
      }
    }
    throw err;
  }
}

Implement circuit breaker pattern:

// Python example with circuit breaker
import redis
from circuitbreaker import circuit

r = redis.Redis(host='localhost', port=6379)

@circuit(failure_threshold=5, recovery_timeout=60)
def cache_write(key, value, ttl=3600):
    try:
        return r.set(key, value, ex=ttl)
    except redis.RedisError as e:
        if 'OOM' in str(e):
            # Circuit breaker will track failures and open if threshold reached
            raise
        return False

# When circuit is open, writes bypass Redis entirely
# Application continues serving from database or computes fresh

Implement data tiering with size limits:

// Limit cache entry size to prevent single keys from consuming too much memory
const MAX_ENTRY_SIZE = 1024 * 1024; // 1MB max per cache entry

async function setWithSizeCheck(key, value, ttl) {
  const serialized = JSON.stringify(value);
  
  if (Buffer.byteLength(serialized) > MAX_ENTRY_SIZE) {
    // Store reference in Redis, actual data in object storage
    const s3Key = await uploadToS3(key, serialized);
    await redis.set(key, JSON.stringify({ _ref: s3Key, _size: Buffer.byteLength(serialized) }), { EX: ttl });
    return true;
  }
  
  return await redis.set(key, serialized, { EX: ttl });
}

Batch writes with memory checks:

// Check memory before bulk operations
async function bulkCache(dataMap, ttl = 3600) {
  const info = await redis.info('memory');
  const usedMemory = parseInt(info.match(/used_memory:(\d+)/)[1]);
  const maxMemory = parseInt(info.match(/maxmemory:(\d+)/)[1]);
  
  const memoryRatio = usedMemory / maxMemory;
  
  if (memoryRatio > 0.85) {
    // Approaching limit - evict 10% of keys before proceeding
    const keys = await redis.keys('cache:*');
    const evictCount = Math.floor(keys.length * 0.1);
    for (let i = 0; i < evictCount; i++) {
      await redis.del(keys[i]);
    }
  }
  
  // Now perform bulk write with pipeline
  const pipeline = redis.pipeline();
  for (const [key, value] of Object.entries(dataMap)) {
    pipeline.set(key, JSON.stringify(value), { EX: ttl });
  }
  return await pipeline.exec();
}

Best Practices to Prevent OOM Errors

Preventing OOM errors requires a combination of configuration, monitoring, and coding discipline. Here are the most effective practices consolidated:

Monitoring and Alerting

No fix is complete without monitoring. Set up alerts to catch memory issues before they become OOM errors:

# Critical Redis metrics to monitor:
# 1. Memory usage ratio (used_memory / maxmemory)
# 2. Evicted keys rate (evicted_keys per second)
# 3. Keyspace size trend (total keys count over time)
# 4. Command rejection rate (OOM errors per minute)

# Example Prometheus alert rule for Redis memory
# Alert when memory usage exceeds 80% of maxmemory
- alert: RedisMemoryUsageHigh
  expr: (redis_memory_used_bytes / redis_memory_max_bytes) > 0.8
  for: 5m
  severity: warning
  annotations:
    summary: "Redis memory usage > 80% for 5 minutes"

- alert: RedisOOMImminent
  expr: (redis_memory_used_bytes / redis_memory_max_bytes) > 0.95
  for: 1m
  severity: critical
  annotations:
    summary: "Redis memory usage > 95% - OOM likely within minutes"

Set up a dashboard tracking these metrics. When evicted_keys spikes or memory ratio crosses 90%, investigate before writes start failing. Many teams also implement automated responses: when memory exceeds 90%, a cron job runs to evict the largest or oldest cache keys preemptively.

Here's a practical monitoring script you can run periodically:

#!/bin/bash
# Redis memory health check - run via cron every 5 minutes

MEM_INFO=$(redis-cli INFO memory)
USED_MEMORY=$(echo "$MEM_INFO" | grep "used_memory:" | cut -d: -f2 | tr -d '\r')
MAX_MEMORY=$(echo "$MEM_INFO" | grep "maxmemory:" | cut -d: -f2 | tr -d '\r')

if [ "$MAX_MEMORY" -gt 0 ]; then
  RATIO=$(( USED_MEMORY * 100 / MAX_MEMORY ))
  
  if [ "$RATIO" -gt 95 ]; then
    echo "CRITICAL: Redis memory at ${RATIO}%" | mail -s "Redis OOM Alert" ops@example.com
    
    # Emergency mitigation: switch to allkeys-lru and evict
    redis-cli CONFIG SET maxmemory-policy allkeys-lru
    redis-cli EVAL "for i=1,100 do redis.call('DEL', redis.call('RANDOMKEY')) end" 0
  elif [ "$RATIO" -gt 85 ]; then
    echo "WARNING: Redis memory at ${RATIO}%" | mail -s "Redis Memory Warning" ops@example.com
  fi
fi

This script provides graduated response: warning at 85%, automatic mitigation at 95%. Adapt the thresholds and notification channels to your infrastructure.

Conclusion

The Redis OOM command not allowed error is a deliberate safeguard that prevents catastrophic memory exhaustion but requires thoughtful configuration to avoid. The four fix categories—increasing maxmemory, changing eviction policy, optimizing memory usage, and implementing application-level fallbacks—work together as defense-in-depth. Start by setting an appropriate maxmemory and a sensible eviction policy like allkeys-lru for caches or volatile-lru for mixed workloads. Then optimize your data structures, enforce TTLs, and build monitoring that alerts you before memory pressure becomes critical. With these practices in place, Redis will gracefully manage memory rather than abruptly rejecting writes, keeping your application reliable and your users unaffected.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles