<?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: Ammar Eyad</title>
    <description>The latest articles on DEV Community by Ammar Eyad (@ammar_eyad).</description>
    <link>https://dev.to/ammar_eyad</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%2F4112066%2F70b82f05-3ad6-4e77-a574-75f1d0ddd6ae.png</url>
      <title>DEV Community: Ammar Eyad</title>
      <link>https://dev.to/ammar_eyad</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ammar_eyad"/>
    <language>en</language>
    <item>
      <title>Common Cache Production</title>
      <dc:creator>Ammar Eyad</dc:creator>
      <pubDate>Wed, 09 Sep 2026 07:07:54 +0000</pubDate>
      <link>https://dev.to/ammar_eyad/common-cache-production-1ni7</link>
      <guid>https://dev.to/ammar_eyad/common-cache-production-1ni7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fplhc116foqj1pihre8xo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fplhc116foqj1pihre8xo.png" alt="Cache Stampede" width="554" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Cache Stampede, Hot Keys, and Cache Penetration
&lt;/h1&gt;

&lt;p&gt;Caching is one of the most common ways to improve application performance and reduce database load.&lt;/p&gt;

&lt;p&gt;But as traffic grows, caching introduces its own problems.&lt;/p&gt;

&lt;p&gt;Three common ones are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cache Stampede&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hot Keys&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache Penetration&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's understand each one with simple examples.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Cache Stampede
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;cache stampede&lt;/strong&gt; (also called the &lt;strong&gt;thundering herd&lt;/strong&gt; or &lt;strong&gt;dogpiling&lt;/strong&gt; problem) happens when a popular cached value expires and many requests try to fetch the same data from the database at the same time.&lt;/p&gt;

&lt;p&gt;Instead of protecting the database, the cache suddenly becomes the reason the database gets overloaded.&lt;/p&gt;

&lt;h3&gt;
  
  
  When and Why Does It Happen?
&lt;/h3&gt;

&lt;p&gt;Normally, we put a cache such as Redis in front of our database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
   ↓
Application
   ↓
  Cache
   ↓
 Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cached data usually has a &lt;strong&gt;Time To Live (TTL)&lt;/strong&gt; so that it doesn't stay stale forever.&lt;/p&gt;

&lt;p&gt;A cache stampede can happen when:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. High Concurrency
&lt;/h3&gt;

&lt;p&gt;Thousands of users request the same piece of data.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET homepage
GET homepage
GET homepage
GET homepage
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Simultaneous Expiration
&lt;/h3&gt;

&lt;p&gt;The cached value reaches its TTL and expires.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cache
product:123 → EXPIRED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Race Condition
&lt;/h3&gt;

&lt;p&gt;The first request goes to the database to retrieve the data.&lt;/p&gt;

&lt;p&gt;But before that request finishes and updates the cache, thousands of other requests also see a cache miss.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request 1 ──→ Cache MISS ──→ Database
Request 2 ──→ Cache MISS ──→ Database
Request 3 ──→ Cache MISS ──→ Database
Request 4 ──→ Cache MISS ──→ Database
...
Request 10000 ─→ Cache MISS ──→ Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now thousands of requests are doing the &lt;strong&gt;same database query&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This can overload the database and potentially cause it to slow down or crash.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Do We Solve a Cache Stampede?
&lt;/h2&gt;

&lt;p&gt;The main goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't allow thousands of requests to perform the same database work simultaneously.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are several approaches.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Locking
&lt;/h3&gt;

&lt;p&gt;When the cache expires, the first request acquires a lock.&lt;/p&gt;

&lt;p&gt;Other requests wait briefly instead of querying the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request 1 → Cache MISS → Acquire Lock → Database
Request 2 → Cache MISS → Wait
Request 3 → Cache MISS → Wait
Request 4 → Cache MISS → Wait
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first request retrieves the data and updates the cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database
   ↓
Update Cache
   ↓
Release Lock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The waiting requests can then read the newly populated cache.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Stale-While-Revalidate
&lt;/h3&gt;

&lt;p&gt;Instead of making users wait for fresh data, we temporarily serve the existing stale value.&lt;/p&gt;

&lt;p&gt;At the same time, a background process refreshes the cache.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Request
     ↓
Stale Cache
     ↓
Return Immediately

Background Process
     ↓
Database
     ↓
Update Cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well when serving slightly outdated data is acceptable.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. External Pre-Warming
&lt;/h3&gt;

&lt;p&gt;Another approach is to refresh the cache &lt;strong&gt;before it expires&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, a scheduled job can periodically load popular data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cron Job
   ↓
Database
   ↓
Update Cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means user traffic doesn't have to be the thing that triggers the cache refresh.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Hot Keys
&lt;/h1&gt;

&lt;p&gt;A &lt;strong&gt;hot key&lt;/strong&gt; happens when a single cache key receives a very large number of requests.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;product:123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10 application servers&lt;/li&gt;
&lt;li&gt;100,000 user requests&lt;/li&gt;
&lt;li&gt;One Redis server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The load balancer distributes requests across the application servers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Load Balancer
                 /    |    \
                /     |     \
            App 1   App 2   App 3 ... App 10
               \       |       /
                \      |      /
                    Redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application servers can scale horizontally.&lt;/p&gt;

&lt;p&gt;But if all requests need the same Redis key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;product:123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then all application servers send requests to Redis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;App 1 ──┐
App 2 ──┤
App 3 ──┤
App 4 ──┤
...     ├──→ Redis
App 10 ─┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is that &lt;strong&gt;one Redis node is receiving a huge amount of traffic for the same key&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Redis CPU or network capacity can become the bottleneck.&lt;/p&gt;




&lt;h2&gt;
  
  
  Solution 1: L1 / L2 Cache
&lt;/h2&gt;

&lt;p&gt;One of the simplest solutions is to introduce a local cache.&lt;/p&gt;

&lt;p&gt;We can think of the caches as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;L1 → Application Server Local Memory
L2 → Redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application checks L1 first.&lt;/p&gt;

&lt;p&gt;If the data isn't there, it checks Redis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
   ↓
L1 Cache
   ↓
 Cache Hit? ── Yes → Return
   │
   No
   ↓
Redis
   ↓
Store in L1
   ↓
Return
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Without L1 Cache
&lt;/h3&gt;

&lt;p&gt;Suppose we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10 application servers&lt;/li&gt;
&lt;li&gt;100,000 requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without L1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100,000 requests
       ↓
100,000 Redis calls
       ↓
Redis becomes the bottleneck
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  With L1 Cache
&lt;/h3&gt;

&lt;p&gt;Each application server can keep the hot data in local memory for a short period, for example &lt;strong&gt;5 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Initially:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;App 1 → Redis
App 2 → Redis
App 3 → Redis
...
App 10 → Redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, each server has the value locally.&lt;/p&gt;

&lt;p&gt;The remaining requests can be served directly from memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100,000 requests
       ↓
   L1 Cache
       ↓
Most requests served locally
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So instead of sending every request to Redis, each application server can serve the majority of requests from its own memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Is L1 So Effective?
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. No Network Overhead
&lt;/h4&gt;

&lt;p&gt;Reading from local memory is much faster than making a network call to Redis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local Memory → Very fast
Redis        → Network round trip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Application Servers Scale Horizontally
&lt;/h4&gt;

&lt;p&gt;You can add more application servers behind a load balancer.&lt;/p&gt;

&lt;p&gt;This helps distribute user traffic across multiple machines.&lt;/p&gt;

&lt;p&gt;A single hot Redis key, however, can still concentrate traffic on one Redis node.&lt;/p&gt;




&lt;h1&gt;
  
  
  Solution 2: Key Splitting
&lt;/h1&gt;

&lt;p&gt;Another approach is to split a hot key into multiple keys.&lt;/p&gt;

&lt;p&gt;In Redis Cluster, a single key is mapped to a single shard.&lt;/p&gt;

&lt;p&gt;So instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;product:123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we can create multiple copies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;product:123:1
product:123:2
product:123:3
...
product:123:100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a request arrives, the application chooses one of the keys.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request 1 → product:123:17
Request 2 → product:123:42
Request 3 → product:123:8
Request 4 → product:123:91
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the traffic can be distributed across different Redis cluster nodes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Idea
&lt;/h3&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100,000 requests
       ↓
product:123
       ↓
One Redis shard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we can have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100,000 requests
       ↓
 ┌─────┼─────┐
 ↓     ↓     ↓
Key 1 Key 2 Key 3 ... Key 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This spreads the read traffic instead of hammering a single Redis key.&lt;/p&gt;




&lt;h1&gt;
  
  
  Solution 3: Read Replicas
&lt;/h1&gt;

&lt;p&gt;If you're using a Redis primary-replica architecture, another option is to add read replicas.&lt;/p&gt;

&lt;p&gt;The basic idea is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              Primary
             /   |   \
            /    |    \
       Replica Replica Replica
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Writes go to the primary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
     ↓
 Primary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reads can be distributed across replicas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
     ↓
 ┌───┼────────┐
 ↓   ↓        ↓
R1   R2       R3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the system to distribute a large number of read requests across multiple Redis nodes.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Cache Penetration
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Cache penetration&lt;/strong&gt; happens when requests repeatedly ask for data that &lt;strong&gt;doesn't exist&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /users/999999
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the user doesn't exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
   ↓
Redis
   ↓
Cache MISS
   ↓
PostgreSQL
   ↓
User NOT FOUND
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem becomes serious when thousands of requests are made for invalid IDs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/user/999999
/user/837462
/user/123456
/user/555555
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every request misses the cache and reaches the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request 1 → Redis MISS → PostgreSQL → NOT FOUND
Request 2 → Redis MISS → PostgreSQL → NOT FOUND
Request 3 → Redis MISS → PostgreSQL → NOT FOUND
...
Request 10000 → Redis MISS → PostgreSQL → NOT FOUND
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike a cache stampede, the problem here isn't that valid cached data expired.&lt;/p&gt;

&lt;p&gt;The problem is that &lt;strong&gt;the requested data doesn't exist in the first place&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Solution: Bloom Filter
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Bloom Filter&lt;/strong&gt; is a memory-efficient data structure that can quickly tell us whether an item &lt;strong&gt;might exist&lt;/strong&gt; in a dataset.&lt;/p&gt;

&lt;p&gt;The application can check the Bloom Filter before querying Redis or PostgreSQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
   ↓
Bloom Filter
   ↓
Could this ID exist?
   /          \
 No            Yes
 ↓              ↓
Return        Redis
Not Found       ↓
             PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, suppose we have these users:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1
2
3
4
5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Bloom Filter knows about these IDs.&lt;/p&gt;

&lt;p&gt;Now a request comes for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;999999
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Bloom Filter can say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;999999 → Definitely does not exist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we can stop the request immediately without hitting Redis or PostgreSQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Important Bloom Filter Property
&lt;/h3&gt;

&lt;p&gt;A Bloom Filter can have &lt;strong&gt;false positives&lt;/strong&gt;, but it does not have false negatives.&lt;/p&gt;

&lt;p&gt;In simple terms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bloom Filter → "Definitely doesn't exist"
        ↓
     Stop here

Bloom Filter → "Might exist"
        ↓
   Check Redis
        ↓
   Check Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So if the Bloom Filter says an ID doesn't exist, we can safely reject the request.&lt;/p&gt;

&lt;p&gt;If it says the ID might exist, we continue normally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Is This Useful?
&lt;/h3&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1,000,000 invalid requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without a Bloom Filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1,000,000 requests
        ↓
      Redis
        ↓
  PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a Bloom Filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1,000,000 requests
        ↓
   Bloom Filter
        ↓
Most invalid requests stopped here
        ↓
Only possible valid requests
        ↓
      Redis
        ↓
   PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can significantly reduce unnecessary traffic to Redis and PostgreSQL.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bloom Filter is especially useful when the database contains a large number of IDs and the system receives many requests for IDs that don't exist.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Cache Stampede vs Hot Key vs Cache Penetration
&lt;/h1&gt;

&lt;p&gt;These three problems are related, but they are not the same.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Main Issue&lt;/th&gt;
&lt;th&gt;Typical Solutions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cache Stampede&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Many requests hit the database after cache expiration&lt;/td&gt;
&lt;td&gt;Locking, stale-while-revalidate, pre-warming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hot Key&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One cache key receives too many requests&lt;/td&gt;
&lt;td&gt;L1 cache, key splitting, read replicas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cache Penetration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requests repeatedly ask for data that doesn't exist&lt;/td&gt;
&lt;td&gt;Bloom Filter, negative caching, validation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Simple Way to Remember
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cache Stampede:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The cache expires and everyone goes to the database.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Hot Key:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Everyone keeps asking Redis for the same key.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Cache Penetration:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Everyone keeps asking for data that doesn't exist.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Understanding the difference helps you choose the right solution when designing a high-scale system.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>webdev</category>
      <category>web</category>
    </item>
    <item>
      <title>Why You Shouldn’t Store Large Files in Your Database: Database vs. Amazon S3</title>
      <dc:creator>Ammar Eyad</dc:creator>
      <pubDate>Mon, 07 Sep 2026 05:05:15 +0000</pubDate>
      <link>https://dev.to/ammar_eyad/why-you-shouldnt-store-large-files-in-your-database-database-vs-amazon-s3-hec</link>
      <guid>https://dev.to/ammar_eyad/why-you-shouldnt-store-large-files-in-your-database-database-vs-amazon-s3-hec</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Storing files in a database instead of S3 introduces two massive architectural bottlenecks as your application scales:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  1. CDN becomes much harder to use
&lt;/h3&gt;

&lt;p&gt;One of the biggest advantages of S3 is how easily it works with a CDN such as Amazon CloudFront or Cloudflare.&lt;/p&gt;

&lt;p&gt;Imagine you have millions of users requesting the same images.&lt;/p&gt;

&lt;p&gt;With S3 and a CDN, the CDN can cache those images at locations around the world. Users can get the file from a nearby edge location instead of requesting it from your application every time.&lt;/p&gt;

&lt;p&gt;This is exactly what we want for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Images&lt;/li&gt;
&lt;li&gt;Videos&lt;/li&gt;
&lt;li&gt;PDFs&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;CSS&lt;/li&gt;
&lt;li&gt;Other static content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now imagine the image is stored as a BLOB inside your database.&lt;/p&gt;

&lt;p&gt;If your images are locked in a database, your &lt;strong&gt;CDN&lt;/strong&gt; is effectively blind to them. Every single image request must hit your web server, which must then execute a heavy database query to fetch the binary data, stream it back to the web server, and then serve it to the client. You are forcing your most expensive piece of infrastructure (the database) to act as a dumb file server, completely bypassing the speed and cost benefits of edge caching.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Database replication becomes heavier
&lt;/h2&gt;

&lt;p&gt;Production databases usually have replicas for &lt;strong&gt;high availability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your database contains mostly structured data, replication is relatively straightforward.&lt;/p&gt;

&lt;p&gt;But imagine your database contains hundreds of gigabytes or even terabytes of images and videos.&lt;/p&gt;

&lt;p&gt;Now those large binary objects also become part of your database replication and backup workload.&lt;/p&gt;

&lt;p&gt;For example, instead of replicating:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Users
Orders
Products
Payments
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you might also be replicating:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;500 GB of images
1 TB of videos
200 GB of documents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That can make replication, backups, and restores significantly heavier.&lt;/p&gt;

&lt;p&gt;The same applies when you need to restore your database.&lt;/p&gt;

&lt;p&gt;A database backup containing huge amounts of binary data can take much longer to restore than a database containing only structured application data.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Your database becomes unnecessarily large
&lt;/h2&gt;

&lt;p&gt;Databases are extremely good at what they are designed for.&lt;/p&gt;

&lt;p&gt;They are great for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Relationships&lt;/li&gt;
&lt;li&gt;Transactions&lt;/li&gt;
&lt;li&gt;Queries&lt;/li&gt;
&lt;li&gt;Indexes&lt;/li&gt;
&lt;li&gt;Structured data&lt;/li&gt;
&lt;li&gt;Consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But storing large files is a different problem.&lt;/p&gt;

&lt;p&gt;Object storage is designed specifically for storing large objects.&lt;/p&gt;

&lt;p&gt;So instead of making the database responsible for everything, we can separate the responsibilities.&lt;/p&gt;

&lt;p&gt;The database stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
Order
Payment
File metadata
File reference
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;S3 stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Images
Videos
PDFs
Documents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each system does what it is good at.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about the physical storage behind S3?
&lt;/h2&gt;

&lt;p&gt;Another interesting question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where does S3 actually store my file?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At the end of the day, the data has to live on physical storage hardware.&lt;/p&gt;

&lt;p&gt;But as an S3 user, you don't need to know whether your particular object is sitting on an HDD, SSD, or which physical disk contains it.&lt;/p&gt;

&lt;p&gt;AWS manages that infrastructure for you.&lt;/p&gt;

&lt;p&gt;You interact with S3 as object storage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-bucket/users/123/profile.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't interact with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Disk 123
Sector 456
SSD 789
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That abstraction is one of the main benefits of object storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the typical architecture look like?
&lt;/h2&gt;

&lt;p&gt;A common approach is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    |
    +--- Database
    |      File metadata
    |
    +--- S3
           Actual file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For uploads, we can even use a presigned URL so the client uploads the file directly to S3 instead of sending the entire file through our application server.&lt;/p&gt;

&lt;p&gt;The database then only needs to know things like the file name, size, content type, and S3 key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is storing BLOBs in a database always wrong?
&lt;/h2&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;There are cases where it can be perfectly reasonable.&lt;/p&gt;

&lt;p&gt;For example, if the files are very small and the application is simple, storing them in the database might be easier and completely acceptable.&lt;/p&gt;

&lt;p&gt;The important thing is to understand the trade-off.&lt;/p&gt;

&lt;p&gt;For a large-scale system, I generally prefer this simple rule:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database for structured data and metadata.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S3/object storage for large files.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It keeps the architecture cleaner, allows the file storage to scale independently, makes CDN integration easier, and avoids turning your primary database into a file server.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>database</category>
    </item>
  </channel>
</rss>
