<?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: Yogendra Singh</title>
    <description>The latest articles on DEV Community by Yogendra Singh (@yogendra_singh_9737a86e38).</description>
    <link>https://dev.to/yogendra_singh_9737a86e38</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%2F3393333%2Ff9339955-a8dd-4013-a276-a366af0a0fd4.png</url>
      <title>DEV Community: Yogendra Singh</title>
      <link>https://dev.to/yogendra_singh_9737a86e38</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yogendra_singh_9737a86e38"/>
    <language>en</language>
    <item>
      <title>One '%' Cost Us 53 Seconds: A Redis, MySQL Buffer Pool Story</title>
      <dc:creator>Yogendra Singh</dc:creator>
      <pubDate>Tue, 04 Aug 2026 18:33:24 +0000</pubDate>
      <link>https://dev.to/yogendra_singh_9737a86e38/one-cost-us-53-seconds-a-redis-mysql-buffer-pool-story-5117</link>
      <guid>https://dev.to/yogendra_singh_9737a86e38/one-cost-us-53-seconds-a-redis-mysql-buffer-pool-story-5117</guid>
      <description>&lt;p&gt;Every engineering team has that one meeting. Someone proudly announces,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"We reduced database traffic by 95% using Redis!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everyone smiles. Graphs look beautiful. CPU usage drops. Someone even starts preparing a "Performance Improvement" presentation.&lt;/p&gt;

&lt;p&gt;Then...&lt;br&gt;
Production throws an alert. A query that had been taking 9 seconds suddenly starts taking 62 seconds. Naturally, Redis becomes the prime suspect.&lt;/p&gt;

&lt;p&gt;Except...&lt;br&gt;
Redis wasn't the problem. It merely exposed a problem that had always existed.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Background
&lt;/h2&gt;

&lt;p&gt;We had an &lt;strong&gt;entity_attribute&lt;/strong&gt; table that stored dynamic attributes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;entity_id&lt;/th&gt;
&lt;th&gt;key&lt;/th&gt;
&lt;th&gt;value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;STATUS&lt;/td&gt;
&lt;td&gt;ACTIVE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;TYPE&lt;/td&gt;
&lt;td&gt;PREMIUM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;103&lt;/td&gt;
&lt;td&gt;STATUS&lt;/td&gt;
&lt;td&gt;BLOCKED&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Almost every API fetched attributes by entity_id. Those lookups were extremely frequent.&lt;/p&gt;

&lt;p&gt;To reduce database load, we cached the attributes in Redis. Instead of hitting MySQL thousands of times every minute, requests were served directly from memory.&lt;/p&gt;

&lt;p&gt;The result?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lower database CPU ✅&lt;/li&gt;
&lt;li&gt;Lower latency ✅&lt;/li&gt;
&lt;li&gt;Happy developers ✅
Until one query reminded us that production always gets the last laugh.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The One Query That Couldn't Use Redis
&lt;/h2&gt;

&lt;p&gt;Most lookups were by entity_id. But one API searched using the last few digits of the value. The query looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;entity_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;entity_attribute&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%123'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the leading wildcard. That tiny % completely changes how MySQL works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why LIKE '%123' Is Expensive
&lt;/h2&gt;

&lt;p&gt;Indexes are like dictionaries. They help MySQL quickly locate values that start with something.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;code&gt;WHERE value LIKE 'ABC%'&lt;/code&gt;&lt;br&gt;
can efficiently use an index.&lt;/p&gt;

&lt;p&gt;But&lt;br&gt;
&lt;code&gt;WHERE value LIKE '%123'&lt;/code&gt;&lt;br&gt;
asks MySQL:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Find every value that ends with 123."&lt;br&gt;
Since MySQL doesn't know where those values begin, it cannot efficiently traverse a normal B-tree index. Instead, it has to inspect a huge number of rows.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words...&lt;br&gt;
The leading % quietly disables one of the database's biggest performance advantages.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Did It Become Worse After Redis?
&lt;/h2&gt;

&lt;p&gt;Before Redis, this table was one of the hottest tables in MySQL. Thousands of reads kept its pages inside the &lt;strong&gt;InnoDB Buffer Pool.&lt;/strong&gt; Even though the query performed a large scan, much of the data was already in memory.&lt;/p&gt;

&lt;p&gt;The query was slow...&lt;br&gt;
but tolerable. &lt;br&gt;
Around 9 seconds.&lt;/p&gt;

&lt;p&gt;After Redis, almost every lookup disappeared. MySQL gradually evicted those pages from the Buffer Pool.&lt;/p&gt;

&lt;p&gt;Now the same scan had to fetch data from disk. Nothing about the SQL changed. Nothing about the indexes changed.&lt;/p&gt;

&lt;p&gt;Only one thing changed. The table became cold.&lt;/p&gt;

&lt;p&gt;The result?&lt;br&gt;
&lt;strong&gt;9 seconds → 62 seconds.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Redis didn't slow MySQL. It simply stopped keeping the table warm.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Investigation
&lt;/h2&gt;

&lt;p&gt;At first, everyone blamed Redis. Then we looked deeper. Execution plans were nearly identical. The real difference was physical I/O.&lt;/p&gt;

&lt;p&gt;The query wasn't CPU-bound anymore. It had become disk-bound. That's when we realized something important.&lt;/p&gt;

&lt;p&gt;Even if we warmed the Buffer Pool again...&lt;br&gt;
The query itself was fundamentally inefficient.&lt;/p&gt;

&lt;p&gt;We needed a better query.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Real Fix
&lt;/h2&gt;

&lt;p&gt;Instead of searching using&lt;br&gt;
&lt;code&gt;WHERE value LIKE '%123'&lt;/code&gt;&lt;br&gt;
We extracted the searchable suffix into a Generated Column.&lt;br&gt;
For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;entity_attribute&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;gen_val&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;RIGHT&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;STORED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we indexed it.&lt;br&gt;
&lt;code&gt;CREATE INDEX idx_gen_val ON entity_attribute(gen_val);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now the query became&lt;br&gt;
&lt;code&gt;SELECT entity_id FROM entity_attribute WHERE gen_val = '123';&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;No wildcard. No table scan. A simple indexed lookup. &lt;br&gt;
Exactly what MySQL loves.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;Instead of asking MySQL to inspect nearly every row...&lt;/p&gt;

&lt;p&gt;We gave it a proper index. The query became dramatically faster.&lt;/p&gt;

&lt;p&gt;More importantly...&lt;br&gt;
Its performance was now independent of whether the table happened to be warm in the InnoDB Buffer Pool.&lt;br&gt;
The optimization wasn't just faster. It was predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cache doesn't fix bad queries.&lt;/strong&gt; It often hides them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lower database traffic changes memory behavior.&lt;/strong&gt;&lt;br&gt;
Redis reduced reads so effectively that MySQL no longer kept this table in the InnoDB Buffer Pool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leading wildcards are expensive.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;LIKE '%123'&lt;br&gt;
is one of those queries that should immediately make every DBA slightly uncomfortable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Generated Columns are underrated.&lt;/strong&gt;&lt;br&gt;
They let you convert an expensive expression into something MySQL can index efficiently, without changing the original data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Redis wasn't the villain. The Buffer Pool wasn't broken.&lt;br&gt;
MySQL wasn't slow.&lt;br&gt;
Our query was asking MySQL to do something it was never optimized to do.&lt;br&gt;
Redis simply removed the accidental performance boost that had been masking the problem.&lt;br&gt;
Sometimes production doesn't create bugs. &lt;strong&gt;It reveals assumptions&lt;/strong&gt;. And in our case, the biggest optimization wasn't Redis.&lt;/p&gt;

&lt;p&gt;It was replacing one innocent-looking % with an indexed generated column.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This may not be the perfect solution. Comment what could have been done better.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>mysql</category>
      <category>performance</category>
      <category>redis</category>
    </item>
  </channel>
</rss>
