<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ace Meridian</title>
    <description>The latest articles on DEV Community by Ace Meridian (@acemeridian).</description>
    <link>https://dev.to/acemeridian</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4241423%2Fa1f0f0a1-28a6-4014-8987-ae05e14af4ad.png</url>
      <title>DEV Community: Ace Meridian</title>
      <link>https://dev.to/acemeridian</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/acemeridian"/>
    <language>en</language>
    <item>
      <title>Redis OOM command not allowed: Set maxmemory-policy</title>
      <dc:creator>Ace Meridian</dc:creator>
      <pubDate>Thu, 12 Nov 2026 16:16:06 +0000</pubDate>
      <link>https://dev.to/acemeridian/redis-oom-command-not-allowed-set-maxmemory-policy-2nhn</link>
      <guid>https://dev.to/acemeridian/redis-oom-command-not-allowed-set-maxmemory-policy-2nhn</guid>
      <description>&lt;p&gt;If Redis is only a cache, run &lt;code&gt;redis-cli CONFIG SET maxmemory-policy allkeys-lru&lt;/code&gt; and put the same setting in redis.conf (or your managed service's parameter group): writes go through again, because Redis now evicts least recently used keys instead of refusing new ones. If the instance also holds queues, sessions or locks, keep &lt;code&gt;volatile-lru&lt;/code&gt; and write every cache key with a TTL (&lt;code&gt;SET key value EX 3600&lt;/code&gt;), because the volatile policies can only evict keys that have one.&lt;/p&gt;

&lt;p&gt;This story was written with the assistance of an AI writing program. I reproduced the error on Redis 8.10.2 with redis-py 8.1.0 and Python 3.14.4, with &lt;code&gt;maxmemory&lt;/code&gt; at 20 MB and 1 KiB values so the instance fills in seconds. Every number below comes from those runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Error Blocks, and What It Doesn't
&lt;/h2&gt;

&lt;p&gt;Under the default policy, &lt;code&gt;noeviction&lt;/code&gt;, the 15,201st &lt;code&gt;SET&lt;/code&gt; failed. redis-py raised &lt;code&gt;redis.exceptions.OutOfMemoryError: command not allowed when used memory &amp;gt; 'maxmemory'.&lt;/code&gt;; redis-cli prints the same text after &lt;code&gt;OOM&lt;/code&gt;. &lt;code&gt;evicted_keys&lt;/code&gt; was 0.&lt;/p&gt;

&lt;p&gt;Not every command failed. &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;EXISTS&lt;/code&gt;, &lt;code&gt;EXPIRE&lt;/code&gt; and &lt;code&gt;DEL&lt;/code&gt; still worked; &lt;code&gt;SET&lt;/code&gt;, &lt;code&gt;INCR&lt;/code&gt; and &lt;code&gt;LPUSH&lt;/code&gt; were refused. Redis rejects commands that can grow memory and lets reads and deletes through, so the outage looks partial: cache reads keep hitting while writes, counters and job enqueues fail. Deleting 1,000 keys let the next &lt;code&gt;SET&lt;/code&gt; in.&lt;/p&gt;

&lt;p&gt;Batches split too. A &lt;code&gt;MULTI&lt;/code&gt;/&lt;code&gt;EXEC&lt;/code&gt; with &lt;code&gt;EXPIRE&lt;/code&gt;, &lt;code&gt;SET&lt;/code&gt; and &lt;code&gt;INCR&lt;/code&gt; was rejected whole, &lt;code&gt;EXPIRE&lt;/code&gt; included. The same three commands in a plain pipeline applied the &lt;code&gt;EXPIRE&lt;/code&gt; and failed the other two.&lt;/p&gt;

&lt;h2&gt;
  
  
  volatile-lru Without TTLs Behaves Like noeviction
&lt;/h2&gt;

&lt;p&gt;Self-hosted Redis defaults to &lt;code&gt;noeviction&lt;/code&gt;; managed services often default to &lt;code&gt;volatile-lru&lt;/code&gt;. I switched to &lt;code&gt;volatile-lru&lt;/code&gt; and refilled with keys that had no TTL. The 15,200th &lt;code&gt;SET&lt;/code&gt; failed with the same error and nothing was evicted: the volatile policies only choose among keys with an expiry, and there were none. Four commands show it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;$ redis-cli CONFIG GET maxmemory-policy
maxmemory-policy
volatile-lru
$ redis-cli INFO keyspace
&lt;span class="c"&gt;# Keyspace&lt;/span&gt;
db0:keys=15199,expires=0,avg_ttl=0,subexpiry=0
$ redis-cli INFO stats &lt;span class="o"&gt;|&lt;/span&gt; grep evicted_keys
evicted_keys:0
$ redis-cli SET probe 1
OOM &lt;span class="nb"&gt;command&lt;/span&gt; not allowed when used memory &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'maxmemory'&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;expires=0&lt;/code&gt; next to thousands of keys means the policy has nothing to evict. With &lt;code&gt;ex=3600&lt;/code&gt; on every write, the same instance accepted 60,000 of 60,000 writes, evicted 45,155 keys and kept 14,845.&lt;/p&gt;

&lt;h2&gt;
  
  
  allkeys-lru Fixes a Cache and Empties a Queue
&lt;/h2&gt;

&lt;p&gt;Under &lt;code&gt;allkeys-lru&lt;/code&gt; with no TTLs, all 60,000 writes succeeded and 44,863 keys were evicted. None of the first 1,000 keys written were left; 982 of the last 1,000 were. For a pure cache, that is the behavior you want.&lt;/p&gt;

&lt;p&gt;Then I put a 3,000-item list named &lt;code&gt;jobs&lt;/code&gt; on the instance before the cache writes, the way small apps share one Redis between a task queue and a cache. Under &lt;code&gt;allkeys-lru&lt;/code&gt; every cache write succeeded and the list was gone: &lt;code&gt;EXISTS jobs&lt;/code&gt; returned 0, and nothing raised an error. Under &lt;code&gt;volatile-lru&lt;/code&gt; with a TTL on each cache key, all 60,000 cache writes succeeded and the list kept its 3,000 items. Under &lt;code&gt;noeviction&lt;/code&gt; the list survived, but cache writes failed after 12,489.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;allkeys-lru&lt;/code&gt; belongs on an instance that holds nothing you cannot recompute. Anything else gets &lt;code&gt;volatile-lru&lt;/code&gt; plus TTLs, or better, its own instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put a TTL on Every Cache Write
&lt;/h2&gt;

&lt;p&gt;This is the write path I tested:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6379&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cache_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# a TTL makes the key evictable under volatile-lru&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OutOfMemoryError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# full and nothing evictable: skip the cache, keep serving&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran it unchanged against a local Redis on port 6379. On a fresh &lt;code&gt;volatile-lru&lt;/code&gt; instance it returned &lt;code&gt;True&lt;/code&gt; 60,000 times out of 60,000 while Redis evicted 45,135 keys. On a full instance whose 15,199 existing keys had no TTL, it returned &lt;code&gt;False&lt;/code&gt; 2,000 times out of 2,000: requests were still served, but nothing new was cached. The &lt;code&gt;except&lt;/code&gt; keeps the app up, so count those &lt;code&gt;False&lt;/code&gt; returns and alert on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making a Full Instance Writable Again
&lt;/h2&gt;

&lt;p&gt;On a full &lt;code&gt;noeviction&lt;/code&gt; instance, &lt;code&gt;CONFIG SET maxmemory-policy allkeys-lru&lt;/code&gt; took effect at once: the next 2,000 cache writes succeeded, with 2,051 evictions. A &lt;code&gt;CONFIG SET&lt;/code&gt; is lost on restart, so persist the change in the config too.&lt;/p&gt;

&lt;p&gt;If the instance has to stay on &lt;code&gt;volatile-lru&lt;/code&gt;, you can add TTLs while it is full, because &lt;code&gt;EXPIRE&lt;/code&gt; is not refused. I set a 600-second TTL on 3,000 keys that had none; all 3,000 calls succeeded, and the next 2,000 writes went through with 2,092 evictions. In production that is a &lt;code&gt;SCAN&lt;/code&gt; over the cache key pattern with an &lt;code&gt;EXPIRE&lt;/code&gt; per match.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Big Write Can Fail Below the Limit
&lt;/h2&gt;

&lt;p&gt;With 12,000 keys, &lt;code&gt;used_memory&lt;/code&gt; was 16,744,312 bytes, about 16 of the 20 MB. One 8 MB &lt;code&gt;SET&lt;/code&gt; was still refused under &lt;code&gt;noeviction&lt;/code&gt;, and &lt;code&gt;used_memory&lt;/code&gt; was below the limit before and after. My reading is that the 8 MB argument is already in Redis's memory when it checks the limit, so the request counts against itself. Under &lt;code&gt;allkeys-lru&lt;/code&gt; the same write succeeded after 4,794 evictions, and &lt;code&gt;used_memory&lt;/code&gt; read 22,416,912 bytes right after, above the 20,971,520 limit. &lt;code&gt;maxmemory&lt;/code&gt; is a target Redis evicts toward, not a hard cap, so leave headroom below the container or VM limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Check &lt;code&gt;CONFIG GET maxmemory-policy&lt;/code&gt; and &lt;code&gt;INFO keyspace&lt;/code&gt; first. &lt;code&gt;noeviction&lt;/code&gt;, or a &lt;code&gt;volatile-*&lt;/code&gt; policy with &lt;code&gt;expires=0&lt;/code&gt;, means Redis has nothing it may evict.&lt;/li&gt;
&lt;li&gt;Pure cache: &lt;code&gt;allkeys-lru&lt;/code&gt;, persisted in config, not only set at runtime.&lt;/li&gt;
&lt;li&gt;Cache plus queues or sessions: &lt;code&gt;volatile-lru&lt;/code&gt; and a TTL on every cache write, or separate instances.&lt;/li&gt;
&lt;li&gt;If none of the data is evictable, the fix is a bigger &lt;code&gt;maxmemory&lt;/code&gt; or less data, not a policy.&lt;/li&gt;
&lt;li&gt;Catch &lt;code&gt;OutOfMemoryError&lt;/code&gt; on cache writes and alert on it; reads keep working, so the outage looks partial.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;MULTI&lt;/code&gt; when writes must land together; a plain pipeline can apply half of them.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>redis</category>
      <category>devops</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
