Troubleshooting Redis 'OOM command not allowed when used memory > maxmemory': Complete Object Cache Runbook

Exhaustive sysadmin guide to resolving Redis (error) OOM command not allowed and RDB fork persistence failures. Diagnose maxmemory eviction policies, memory fragmentation, vm.overcommit_memory, and WooCommerce transient leaks.

Troubleshooting Redis 'OOM command not allowed when used memory > maxmemory': Complete Object Cache Runbook

Few infrastructure incidents disrupt a high-traffic WordPress or WooCommerce site more rapidly than a locked Redis instance. Without warning, write queries crash across the application stack, cart checkouts throw critical database errors, background cron jobs freeze, and application logs fill with a fatal exception:

(error) OOM command not allowed when used memory > 'maxmemory'.

At the exact same moment, site administrators frequently observe a baffling paradox: read operations like GET and MGET continue to succeed, while write commands (SET, HSET, LPUSH) fail instantly. Soon after, background snapshotting locks up with:

MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled.

This diagnostic runbook provides systems administrators, DevOps engineers, and WooCommerce operators with an architectural breakdown of Redis memory internals, the five primary root causes behind Redis OOM failures, deep CLI telemetry analysis, and an actionable configuration blueprint for /etc/redis/redis.conf and the Linux kernel.


1. Architectural Internals: How Redis Manages Memory

To diagnose and resolve out-of-memory errors permanently, you must first understand how Redis manages memory allocations, how it tracks process boundaries, and where it intercepts commands inside its single-threaded event loop.

The Jemalloc Memory Allocator

Redis does not allocate raw memory directly from the operating system for every key, string, or hash entry. Instead, it delegates memory allocation to jemalloc (or glibc malloc on custom builds). Jemalloc categorizes allocations into discrete size classes (small, large, and huge bins) to optimize throughput and mitigate fragmentation.

However, memory allocators manage memory in pages (typically 4 KB chunks on Linux x86_64). When Redis deletes a key or evicts an entry, jemalloc frees the space within its internal arenas, but it does not immediately return that physical memory back to the Linux kernel via madvise() or brk(). This leads to a divergence between two critical metrics:

  • used_memory: The total byte count of memory that Redis allocated via jemalloc to store keys, values, and internal data structures.
  • used_memory_rss (Resident Set Size): The actual number of physical RAM pages that the Linux kernel has assigned to the Redis process in physical memory.
+-----------------------------------------------------------------------+
|                         Physical Host RAM                             |
|                                                                       |
|   +---------------------------------------------------------------+   |
|   |             Resident Set Size (used_memory_rss)               |   |
|   |                                                               |   |
|   |   +-------------------------------------------------------+   |   |
|   |   |      Jemalloc Arenas & Active Pages                   |   |   |
|   |   |                                                       |   |   |
|   |   |   +-----------------------+   +-------------------+   |   |   |
|   |   |   | Active Keys & Hashes  |   | Free Page Holes   |   |   |   |
|   |   |   | (used_memory)         |   | (Fragmentation)   |   |   |   |
|   |   |   +-----------------------+   +-------------------+   |   |   |
|   |   +-------------------------------------------------------+   |   |
|   +---------------------------------------------------------------+   |
+-----------------------------------------------------------------------+

When used_memory_rss significantly exceeds used_memory, the server experiences high memory fragmentation.

The Eviction Cycle in the Event Loop

Redis executes commands sequentially on a single core using an event-driven multiplexing loop (aeProcessEvents). Before processing any incoming command, Redis invokes the internal function processCommand().

Within processCommand(), Redis evaluates whether the incoming command is flagged with the CMD_DENYOOM attribute. Nearly all write operations—including SET, SETEX, HSET, LPUSH, SADD, and ZADD—carry this flag. Conversely, read operations like GET, MGET, HGETALL, and EXISTS do not.

If the command modifies memory, Redis checks the following condition:

/* Pseudocode representing Redis processCommand() memory check */
if (server.maxmemory && !server.lua_timedout) {
    int out_of_memory = (performEvictions() == EVICTION_FAIL);
    if (out_of_memory) {
        if (cmd->flags & CMD_DENYOOM) {
            flagTransaction(c);
            addReply(c, shared.oomerr);
            return C_OK;
        }
    }
}

If used_memory exceeds the configured maxmemory threshold, Redis halts command execution and enters performEvictions(). Redis attempts to sample keys and evict them based on the active maxmemory-policy.

If performEvictions() fails to reclaim enough bytes to bring used_memory below maxmemory—either because the policy is set to noeviction, all keys are exempt, or eviction loops timed out—Redis refuses the write command and returns (error) OOM command not allowed. Meanwhile, read queries continue to execute normally because they bypass the CMD_DENYOOM safety gate.


2. The Five Root Causes of Redis OOM and Persistence Failures

A Redis OOM error is rarely an isolated glitch; it is the direct symptom of mismatched configuration, unbounded data growth, or operating system memory overcommit restrictions.

Cause 1: noeviction Default Policy with Unbounded Growth

By default, standard open-source Redis packages ship with:

maxmemory 0
maxmemory-policy noeviction

When maxmemory is set to 0 on a 64-bit system, Redis imposes no memory ceiling whatsoever and will allocate until the operating system runs out of physical memory.

If a system administrator sets maxmemory 2gb but retains maxmemory-policy noeviction, Redis will never automatically purge old or expired keys to free up space. Once the 2 GB threshold is reached, every subsequent write command is rejected outright, breaking WordPress object caching and halting application sessions.

Cause 2: Sub-optimal Eviction Policies (volatile-lru vs allkeys-lru)

Many production setups configure maxmemory-policy volatile-lru or volatile-lfu. Under volatile-* policies, Redis only evicts keys that have an explicit Time-To-Live (TTL / expiration) set.

In the WordPress and WooCommerce ecosystems, this causes silent memory exhaustion:

  • Standard WordPress transients created via set_transient() store expiration times as separate database rows rather than native Redis key TTLs if certain legacy object cache drop-ins are used.
  • Malformed plugins often insert persistent options, WooCommerce session tokens, or cart caches without expiration flags.
  • Because these keys lack a native TTL, volatile-lru considers them non-evictable.

As non-expiring keys accumulate, the evictable memory pool shrinks to zero. Redis breaches maxmemory, finds no valid candidates to discard under volatile-lru, and throws the OOM error.

Cause 3: Severe Memory Fragmentation (mem_fragmentation_ratio > 1.5)

Memory fragmentation occurs when Redis repeatedly writes, modifies, and deletes keys of fluctuating sizes (e.g., serialized WordPress post queries, dynamic user cart sessions, and transient HTML fragments). Jemalloc allocates memory in predefined bins, leaving small pockets of unallocated memory distributed across active operating system pages.

If your Redis instance reports:

used_memory: 2.1G
used_memory_rss: 3.9G
mem_fragmentation_ratio: 1.85

Redis has only allocated 2.1 GB of actual key-value data, but the operating system kernel has committed 3.9 GB of physical RAM to the Redis process. If the physical host or VPS has only 4 GB of RAM, the Linux OOM Killer will terminate Redis, or background fork snapshotting will fail, even though used_memory appears well within limits.

Cause 4: Background RDB Snapshot Fork Failure (vm.overcommit_memory = 0)

Redis maintains persistence via background snapshots (RDB) and append-only files (AOF rewrite). When Redis executes BGSAVE, it invokes the Linux fork() system call to create a child process.

The child process shares the parent process’s memory space via Linux Copy-on-Write (CoW). Under high write loads, modified memory pages are copied. The Linux kernel uses a heuristic memory overcommit policy by default (vm.overcommit_memory = 0). When Redis attempts to fork(), the kernel estimates that the child process might need an identical amount of memory as the parent.

If free system RAM is less than the current memory footprint of Redis, the kernel rejects the fork() call with ENOMEM. Redis then enters a defensive safe-mode and logs:

MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled.

Cause 5: Blocking Eviction Stalls on Giant Keys / Large Hash Sets

Redis handles eviction synchronously on the main thread by default. If a WooCommerce plugin or analytical tracking script writes a massive hash set, list, or sorted set containing hundreds of thousands of members into a single key (e.g., wc_session_data or a bloated query cache), evicting that single key requires traversing and freeing tens of thousands of internal pointers.

When Redis triggers eviction on a multi-megabyte key:

  1. The single-threaded event loop blocks for 150 ms to over 1,500 ms while freeing memory.
  2. In-flight client connections from PHP-FPM pool workers time out.
  3. The eviction loop hits internal execution limits, fails to release sufficient aggregate memory in time, and triggers immediate write-rejection exceptions.

3. Real-Time Diagnostic Procedures

When an alert fires, execute these diagnostic commands via redis-cli and the Linux shell to determine the exact failure vector.

+-------------------------------------------------------------------------+
|                  Redis OOM Triage Flowchart                             |
+-------------------------------------------------------------------------+
                                   |
                         redis-cli INFO memory
                                   |
         +-------------------------+-------------------------+
         |                                                   |
used_memory >= maxmemory?                           mem_fragmentation_ratio > 1.5?
         |                                                   |
         v                                                   v
Check eviction policy                               Check OS RSS vs Redis alloc
INFO stats -> evicted_keys                          Enable activedefrag
Inspect --bigkeys & --memkeys                       Tune jemalloc settings
Switch to allkeys-lru                               Restart if allocator jammed

Step 1: Query Memory Telemetry via INFO memory

Connect to your Redis instance and examine the core memory metrics:

redis-cli INFO memory

Expected output and interpretation:

# Memory
used_memory:3221225472
used_memory_human:3.00G
used_memory_rss:4194304000
used_memory_rss_human:3.91G
used_memory_peak:3435973836
used_memory_peak_human:3.20G
total_system_memory:4124397568
total_system_memory_human:3.84G
maxmemory:3221225472
maxmemory_human:3.00G
maxmemory_policy:volatile-lru
mem_fragmentation_ratio:1.30
mem_allocator:jemalloc-5.3.0

Diagnostic Evaluation:

  1. used_memory vs maxmemory: If used_memory is within a few megabytes of maxmemory, your instance is in active eviction mode.
  2. maxmemory_policy: If this is noeviction or volatile-lru on an object cache, this is the root cause of write rejection.
  3. mem_fragmentation_ratio:
    • 1.00 to 1.30: Healthy memory utilization.
    • > 1.50: Severe memory fragmentation. Jemalloc holds OS pages containing sparse data.
    • < 1.00: The operating system has swapped Redis memory to disk (paging), causing severe latency spikes.

Step 2: Identify Memory-Hogging Keys

Scan the dataset to isolate outsized keys that block the eviction loop or consume excessive RAM:

# Scan for the largest keys by datatype
redis-cli --bigkeys

# Sample memory usage across keys (Redis 6+)
redis-cli --memkeys

Sample output:

[00.00%] Biggest string found so far '"wp_transient_plugin_updates"' with 24192 bytes
[12.45%] Biggest hash   found so far '"wc_sessions:active_carts"' with 184912 fields (84102912 bytes)
[45.10%] Biggest list   found so far '"resque:queue:analytics"' with 420199 items (32194816 bytes)

If you discover individual keys exceeding 5 MB to 10 MB in a WordPress object cache, configure the application to split the data across distinct keys or utilize a different persistence layer.

Step 3: Monitor Live Eviction Velocity

Check whether Redis is actively purging keys or completely stalled:

redis-cli INFO stats | grep -E 'evicted_keys|expired_keys|rejected_connections'

To watch live evictions in real time:

watch -n 1 "redis-cli INFO stats | grep -E 'evicted_keys|expired_keys|instantaneous_ops_per_sec'"

If used_memory >= maxmemory and evicted_keys is not incrementing while write requests arrive, your eviction policy is refusing candidate selection, or your dataset consists entirely of non-evictable keys under the current policy.

Step 4: Inspect Kernel Memory Overcommit and OOM-Killer

Verify whether kernel policies or the Linux Out-Of-Memory killer have intervened:

# Check current overcommit configuration (0 = heuristic, 1 = always overcommit)
sysctl vm.overcommit_memory

# Inspect dmesg for kernel kills
dmesg -T | grep -i -E 'oom-killer|redis-server'

If dmesg outputs lines such as Out of memory: Kill process [PID] (redis-server), the host exhausted physical RAM and swap space, forcing the kernel to terminate the Redis process.


4. Production Configuration Runbook

To establish a production-grade Redis cache environment for WordPress, WooCommerce, and high-concurrency microservices, apply the following optimizations across /etc/redis/redis.conf and the Linux kernel.

Sizing maxmemory Correctly

Never set maxmemory to 100% of physical system RAM. Operating system processes, SSH, web servers, and especially Redis RDB snapshotting require unallocated headroom.

  • Dedicated Cache Instance (No RDB persistence, save ""): Allocate 75% to 80% of available physical RAM.
  • Persistent Instance (RDB / AOF enabled): Allocate 50% to 60% of total physical RAM to provide buffer space for Linux Copy-on-Write memory duplication during BGSAVE and BGREWRITEAOF.

Example configuration for an 8 GB server running Redis as a pure object cache:

# /etc/redis/redis.conf
maxmemory 6442450944

Selecting the Eviction Policy for WordPress & WooCommerce

For WordPress and WooCommerce object caching, always use allkeys-lru or allkeys-lfu. Never use volatile-lru or noeviction.

  • allkeys-lru: Evicts the least recently used keys out of the entire keyspace, regardless of whether a TTL is explicitly defined.
  • allkeys-lfu: Evicts the least frequently used keys, preserving frequently accessed keys (such as sitewide autoload options and navigation menus) even if they have not been requested in the last few minutes.
# /etc/redis/redis.conf
maxmemory-policy allkeys-lru

Tuning Eviction Sampling Accuracy

Redis uses an approximated LRU algorithm rather than an exact mathematical linked list to maintain O(1) performance without memory overhead. It selects a random sample of keys and evicts the best candidate among that sample.

# Default is 5. Increase to 10 for near-perfect LRU curves at negligible CPU cost
maxmemory-samples 10
Sample Size Accuracy Relative to True LRU CPU Overhead Recommended Use Case
3 Low (approximate) Minimal High-write, low-memory legacy VPS
5 Standard default (~80% match) Baseline General development servers
10 High (~98% match to true LRU) Low (~1% single core) Production WooCommerce / Object Caches

Enabling Asynchronous Non-Blocking Reclamation

By default, key eviction and key expiration run synchronously on the main event thread. When large keys are reclaimed, the event loop stalls.

Enable Redis Lazy Freeing (active background memory reclamation via dedicated worker threads):

# /etc/redis/redis.conf
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes
replica-lazy-flush yes

With lazyfree-lazy-eviction yes, when maxmemory is reached, Redis unlinks the evicted key from the keyspace in O(1) time on the main thread and delegates memory deallocation to a background pthread, eliminating client latency spikes.

Active Defragmentation Tuning

Enable jemalloc active defragmentation to compact memory pages on the fly without restarting the Redis daemon:

# /etc/redis/redis.conf
activedefrag yes
active-defrag-ignore-bytes 100mb
active-defrag-threshold-lower 10
active-defrag-threshold-upper 30
active-defrag-cycle-min 5
active-defrag-cycle-max 50
active-defrag-max-scan-fields 1000
  • activedefrag yes: Enables runtime memory compaction.
  • active-defrag-ignore-bytes 100mb: Defragmentation will not start unless wasted memory is at least 100 MB.
  • active-defrag-threshold-lower 10: Starts defragmentation when fragmentation reaches 10%.
  • active-defrag-cycle-max 50: Restricts defragmentation CPU consumption to a maximum of 50% of the core.

Fixing Kernel Memory Overcommit

To prevent fork() failures during background snapshotting, configure the Linux kernel to allow memory overcommit unconditionally:

Create /etc/sysctl.d/99-redis.conf:

# /etc/sysctl.d/99-redis.conf
vm.overcommit_memory = 1

Apply the change immediately without rebooting:

sysctl -p /etc/sysctl.d/99-redis.conf

Confirm the active state:

cat /proc/sys/vm/overcommit_memory
# Output must be: 1

5. Summary Reference: Redis Configuration Audit Matrix

Verify your current Redis deployment against this production readiness matrix:

Directive Risky Default / Anti-Pattern Recommended Production Setting Technical Rationale
maxmemory 0 (Unbounded) 70-75% of physical host RAM Prevents host-level Linux kernel OOM kills.
maxmemory-policy noeviction allkeys-lru or allkeys-lfu Evicts oldest/least-used keys regardless of TTL bugs.
maxmemory-samples 5 10 Delivers production-accurate LRU eviction curves.
lazyfree-lazy-eviction no yes Eliminates main-thread blocking stalls during memory reclamation.
activedefrag no yes Reclaims fragmented memory pages in the background.
vm.overcommit_memory 0 1 Eliminates RDB fork BGSAVE snapshot failures.

6. Infrastructure Scaling: Moving Beyond Virtualized Memory Contention

While software and kernel tuning resolve configuration bottlenecks, high-throughput WooCommerce stores, Redis session clusters, and high-concurrency microservices frequently encounter the physical boundaries of virtualization.

The Virtualization Problem: Hypervisor RAM Contention

On shared cloud VPS instances and virtual machines, Redis performance often degrades unpredictably due to:

  1. Memory Ballooning: Hypervisors reclaim unallocated or fragmented guest memory pages to serve adjacent virtual machines, causing Redis memory access latency to spike.
  2. Noisy Neighbor I/O Stalls: When an adjacent tenant on a shared host saturates storage I/O, Redis AOF writes or RDB snapshots stall, triggering command pipeline queues.
  3. vCPU Steal Time: Redis is single-threaded. When the hypervisor preempts the virtual CPU running the Redis event loop, all connected web workers block, causing connection timeouts and false cache misses.

Dedicated Bare-Metal Architecture

For enterprise e-commerce platforms processing thousands of concurrent database transactions, running Redis alongside database clusters on unmetered Dedicated Servers provides guaranteed hardware isolation:

  • Unconstrained Physical ECC DDR5 Memory: Zero memory ballooning, deterministic memory latency, and dedicated RAM arrays immune to hypervisor page swapping.
  • Dedicated Physical Cores: Raw CPU core execution with zero virtualization overhead or vCPU scheduling latency for the single-threaded Redis event loop.
  • Ultra-Low Latency NVMe Storage: Microsecond disk persistence for Append-Only Files (AOF) and background RDB snapshotting without write-block contention.

For Pakistani enterprises, fintech platforms, and national retail chains, hosting on domestic Dedicated Servers in Pakistan ensures sub-15ms round-trip latency to local visitors, full regulatory compliance for domestic data residency, and direct peering with major national internet service providers.


Enterprise Database & In-Memory Infrastructure

Eliminate Redis Bottlenecks with Dedicated Performance

Scale your high-traffic WordPress, WooCommerce, and microservice stacks on dedicated bare-metal infrastructure engineered for high-throughput memory caching. Enjoy unmetered ECC DDR5 RAM, dedicated AMD EPYC processors, and 24/7 proactive monitoring.

Explore Dedicated Servers → View Pakistan Dedicated Servers