<?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: Muralidharan Lakshmanan</title>
    <description>The latest articles on DEV Community by Muralidharan Lakshmanan (@muralidharan_lakshmanan).</description>
    <link>https://dev.to/muralidharan_lakshmanan</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%2F3984387%2F7875712e-b548-4f68-95d6-01053bcb6c52.png</url>
      <title>DEV Community: Muralidharan Lakshmanan</title>
      <link>https://dev.to/muralidharan_lakshmanan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/muralidharan_lakshmanan"/>
    <language>en</language>
    <item>
      <title>Cache Patterns Every Engineer Should Know</title>
      <dc:creator>Muralidharan Lakshmanan</dc:creator>
      <pubDate>Sat, 15 Aug 2026 22:33:53 +0000</pubDate>
      <link>https://dev.to/muralidharan_lakshmanan/cache-patterns-every-engineer-should-know-ibf</link>
      <guid>https://dev.to/muralidharan_lakshmanan/cache-patterns-every-engineer-should-know-ibf</guid>
      <description>&lt;p&gt;In the previous parts of this series, we answered two questions: why do we need caching, and where should the cache live?&lt;/p&gt;

&lt;p&gt;Now we need a more practical one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How should the application actually interact with the cache?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is where caching patterns come in. A cache is not just a box where we put data. We need a strategy for reading data, handling misses, writing data, updating cached values, and dealing with staleness. Different patterns solve these problems differently. Let's look at the most common ones.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Cache-Aside — the most common pattern
&lt;/h2&gt;

&lt;p&gt;Let's start with &lt;strong&gt;Cache-Aside&lt;/strong&gt;, also called lazy loading. The basic idea: the application is responsible for checking the cache and loading data on a miss.&lt;br&gt;
&lt;/p&gt;

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

Application → Cache → HIT → Product. Done.

Application → Cache → MISS
                        ↓
                    Database
                        ↓
                  Store in cache
                        ↓
                    Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"product:123"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findProduct&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"product:123"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next request can be served from the cache.&lt;/p&gt;

&lt;p&gt;Cache-Aside is popular because it's simple. The application explicitly decides what to cache, when to cache it, what TTL to use, what to do on a miss, and when to invalidate it. It also works with almost any cache technology.&lt;/p&gt;

&lt;p&gt;The downside is that the application now carries caching logic. Developers need to remember the check-fallback-populate sequence, and if several services do this independently, that logic tends to get duplicated. Still, Cache-Aside is often the best starting point.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Read-Through — let the cache do the loading
&lt;/h2&gt;

&lt;p&gt;Read-Through moves that responsibility away from the application. Instead of the application saying &lt;em&gt;"if the cache misses, I'll query the database,"&lt;/em&gt; it simply says &lt;em&gt;"give me the data."&lt;/em&gt; The cache handles the miss itself.&lt;/p&gt;

&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%2F1qv920du7o8t5v48zdfc.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%2F1qv920du7o8t5v48zdfc.png" alt="Who handles the cache miss: in Cache-Aside the application falls back to the database directly; in Read-Through the cache queries the database on its own and the application only ever talks to the cache" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The application code gets simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"product:123"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the cache doesn't have it, the cache layer knows how to retrieve it. The application doesn't need to know as much about the underlying data source, which can produce cleaner application code.&lt;/p&gt;

&lt;p&gt;The catch is that not every cache supports this natively — you need a caching layer or framework that knows how to load the missing data, which means more abstraction and configuration. Read-Through can be elegant, but Cache-Aside is usually easier to understand and implement directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Write-Through — write to the cache and database together
&lt;/h2&gt;

&lt;p&gt;So far we've mostly talked about reads. But what happens when data changes? Suppose a customer updates their address. Now the database and the cache both need to reflect the new value.&lt;/p&gt;

&lt;p&gt;With Write-Through, a write goes through the cache, and the cache updates the underlying data store as part of the same operation:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The main benefit is freshness — the cache is updated as part of the write path, so the system reduces the chance of serving an old value. The cost is that writes become more expensive: instead of &lt;code&gt;Application → Database&lt;/code&gt;, a write now involves &lt;code&gt;Application → Cache → Database&lt;/code&gt;. We're trading write performance and complexity for better cache freshness.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Write-Behind — make writes fast
&lt;/h2&gt;

&lt;p&gt;Now the opposite approach. What if writes are extremely frequent — say, 50,000 updates per second? Writing every one immediately to the database may be expensive.&lt;/p&gt;

&lt;p&gt;With Write-Behind, the cache accepts the update first and the write returns immediately. The database catches up later, asynchronously.&lt;/p&gt;

&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%2F9h73wufpvnwgoslh28fb.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%2F9h73wufpvnwgoslh28fb.png" alt="Write-Through vs. Write-Behind: Write-Through only returns once both the cache and database are updated, while Write-Behind returns as soon as the cache is updated and persists to the database later — risking data loss if the cache fails before that happens" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This can make writes extremely fast. But there's a serious trade-off: what happens if the cache crashes before the update reaches the database? Potentially, data loss.&lt;/p&gt;

&lt;p&gt;That's why Write-Behind shouldn't be treated as simply "a faster Write-Through." It's a fundamentally different consistency and durability model. It works best when eventual persistence is acceptable, the cache has reliable durability mechanisms of its own, updates can be replayed or recovered, and extreme write performance genuinely matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Refresh-Ahead — don't wait for the cache to expire
&lt;/h2&gt;

&lt;p&gt;Here's another problem. Suppose &lt;code&gt;product:123&lt;/code&gt; is cached with a TTL of 10 minutes, and thousands of users are requesting it. At minute 9, every request is a hit. At minute 10, the cache expires — and thousands of requests can arrive at the database at the same instant.&lt;/p&gt;

&lt;p&gt;We touched on this earlier in the series. It's commonly called a &lt;strong&gt;cache stampede&lt;/strong&gt; or &lt;strong&gt;thundering herd&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Refresh-Ahead tries to avoid it. Instead of waiting for the value to expire, the system refreshes it shortly beforehand, while the existing cached value keeps serving requests in the meantime.&lt;/p&gt;

&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%2Fv4wfagjxt4hxe7rj1tfa.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%2Fv4wfagjxt4hxe7rj1tfa.png" alt="Refresh-Ahead avoids the thundering herd: without it, every request piles onto the database the instant the TTL expires; with it, a background refresh updates the value before expiry so every request stays a hit" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Imagine a flight-search application where a popular route is requested thousands of times. Instead of letting the cache expire completely, the system refreshes the data once it has, say, 30 seconds left. The next request doesn't have to wait for a database call — it just gets served from an already-fresh cache.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Comparing the patterns
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Who handles the miss?&lt;/th&gt;
&lt;th&gt;How writes work&lt;/th&gt;
&lt;th&gt;Main benefit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cache-Aside&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Application&lt;/td&gt;
&lt;td&gt;Application manages writes&lt;/td&gt;
&lt;td&gt;Simple and flexible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Read-Through&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cache&lt;/td&gt;
&lt;td&gt;Depends on implementation&lt;/td&gt;
&lt;td&gt;Cleaner application code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Write-Through&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cache layer&lt;/td&gt;
&lt;td&gt;Cache and database together&lt;/td&gt;
&lt;td&gt;Better freshness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Write-Behind&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cache&lt;/td&gt;
&lt;td&gt;Database updated later&lt;/td&gt;
&lt;td&gt;Very fast writes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Refresh-Ahead&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cache / background process&lt;/td&gt;
&lt;td&gt;Usually a normal write strategy&lt;/td&gt;
&lt;td&gt;Reduces cold misses&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There is no universally "best" pattern. The workload determines the answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. A real-world example
&lt;/h2&gt;

&lt;p&gt;Let's imagine an e-commerce product with a name, description, price, inventory count, and reviews. Should all of it use the same caching pattern? Probably not.&lt;/p&gt;

&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%2Fv6flxdo7n99wd0v2gnm2.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%2Fv6flxdo7n99wd0v2gnm2.png" alt="One product, four caching strategies: the description uses plain Cache-Aside with a long TTL, price adds explicit invalidation, inventory uses a short TTL or bypasses the cache, and recommendations combine Cache-Aside with Refresh-Ahead" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;description&lt;/strong&gt; changes rarely, so plain Cache-Aside with a one-hour TTL is fine. The &lt;strong&gt;price&lt;/strong&gt; changes more often, so we pair Cache-Aside with explicit invalidation the moment it changes. &lt;strong&gt;Inventory&lt;/strong&gt; is more sensitive — a stale number could let a customer buy something that's no longer available, so a much shorter TTL, or skipping the cache in some parts of the workflow, makes more sense. &lt;strong&gt;Recommendations&lt;/strong&gt; can be expensive to calculate, which makes them a good candidate for Cache-Aside plus Refresh-Ahead.&lt;/p&gt;

&lt;p&gt;That's an important lesson: &lt;strong&gt;don't choose one caching pattern for your entire system. Choose the pattern based on the behavior of the data.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  8. The hidden complexity: cache invalidation
&lt;/h2&gt;

&lt;p&gt;Suppose the database has &lt;code&gt;product:123&lt;/code&gt; at &lt;code&gt;$899&lt;/code&gt;, but the cache still has &lt;code&gt;$999&lt;/code&gt;. A caching pattern doesn't automatically solve this — you still need to decide what happens to the cache when the database changes.&lt;/p&gt;

&lt;p&gt;The common answers are to delete the entry and let the next request reload it, update the cached value directly, let the TTL expire naturally, or invalidate the cache in response to a published event when the database changes.&lt;/p&gt;

&lt;p&gt;This is a big enough topic that the next part of this series is devoted entirely to it.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Which pattern should you start with?
&lt;/h2&gt;

&lt;p&gt;If you're designing a new application and aren't sure what to use, don't reach for the most sophisticated pattern first. For many read-heavy applications, plain Cache-Aside in front of the database is an excellent starting point.&lt;/p&gt;

&lt;p&gt;Then measure. Is the hit ratio good? Is database load actually reduced? Are misses expensive? Are hot keys causing problems? Is stale data acceptable? Are writes becoming a bottleneck? Only once you have answers should you introduce something more sophisticated.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. The engineer's mental model
&lt;/h2&gt;

&lt;p&gt;Here's a short way to hold all five patterns in your head at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache-Aside&lt;/strong&gt; — "I'll manage the cache myself."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-Through&lt;/strong&gt; — "The cache will load missing data for me."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write-Through&lt;/strong&gt; — "When I write, update the cache and the source together."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write-Behind&lt;/strong&gt; — "I'll make the cache the fast write point and persist later."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refresh-Ahead&lt;/strong&gt; — "Don't let popular data go cold if I can refresh it proactively."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once these five ideas are second nature, most caching architectures become much easier to reason about.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;We've now covered why caching exists, how it actually works, where it should live, and the patterns that govern how an application talks to it.&lt;/p&gt;

&lt;p&gt;But there is one caching problem that almost every engineer eventually runs into:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you make cached data disappear when the real data changes?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's cache invalidation — often called the hardest problem in caching — and it's next.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Which pattern is running in your production system right now, and did your team choose it deliberately or inherit it? Curious to hear in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>caching</category>
      <category>systemdesign</category>
      <category>backend</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Where Should the Cache Live?</title>
      <dc:creator>Muralidharan Lakshmanan</dc:creator>
      <pubDate>Fri, 14 Aug 2026 22:24:26 +0000</pubDate>
      <link>https://dev.to/muralidharan_lakshmanan/where-should-the-cache-live-2ck6</link>
      <guid>https://dev.to/muralidharan_lakshmanan/where-should-the-cache-live-2ck6</guid>
      <description>&lt;p&gt;Earlier in this series, we established &lt;em&gt;why&lt;/em&gt; caching exists, then looked at &lt;em&gt;how&lt;/em&gt; a cache works — memory, key-value storage, TTL, eviction, locality, and performance.&lt;/p&gt;

&lt;p&gt;Now comes an architectural question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where should the cache actually live?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is where caching gets more interesting.&lt;/p&gt;

&lt;p&gt;A cache doesn't have to be one Redis server sitting between your application and database. In a modern system, you might have several caching layers stacked between the user and the source of truth.&lt;/p&gt;

&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%2F6y604qv88nzew0m24eba.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%2F6y604qv88nzew0m24eba.png" alt="Five places a cache can live: browser cache, CDN, application local cache, distributed cache, and the database, each falling through to the next on a miss" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The closer the cache is to the user or application, the less work the request needs to do.&lt;/p&gt;

&lt;p&gt;But there's a catch: &lt;strong&gt;every additional caching layer introduces another copy of your data — and another thing you have to manage.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Browser cache: the fastest cache you don't own
&lt;/h2&gt;

&lt;p&gt;Let's start at the edge closest to the user: their browser.&lt;/p&gt;

&lt;p&gt;Suppose you visit a website and download &lt;code&gt;logo.png&lt;/code&gt;, &lt;code&gt;app.js&lt;/code&gt;, &lt;code&gt;styles.css&lt;/code&gt;, some fonts, and a set of product images. Do we really need to download those files every time you visit?&lt;/p&gt;

&lt;p&gt;No. The browser can store them locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   │
   ├── Do I already have it?
   │
   └── YES → use the local copy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In many cases the server doesn't even need to process the request. That's an enormous performance improvement, because the network disappears from the critical path entirely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Without browser cache:     With browser cache:

User                       User
  ↓                          ↓
Internet                   Browser cache
  ↓                          ↓
Server                     Response
  ↓
Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  But there is a problem
&lt;/h3&gt;

&lt;p&gt;You don't completely control the browser's copy.&lt;/p&gt;

&lt;p&gt;Imagine you release a new version of your JavaScript application. A user might still be holding &lt;code&gt;app.js&lt;/code&gt; version 1 while your server is serving version 2.&lt;/p&gt;

&lt;p&gt;This is why cache-control headers, versioned filenames, and content hashing matter so much:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.abc123.js
app.def456.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the content changes, the filename changes too. The browser can safely cache the old version forever, because the new application asks for a different file.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. CDN: bringing the cache closer to the user
&lt;/h2&gt;

&lt;p&gt;Now imagine your application runs in Virginia, but your users are in New York, California, London, Singapore, and Tokyo. If every image request travels all the way to your application servers, you're doing unnecessary work.&lt;/p&gt;

&lt;p&gt;This is where a &lt;strong&gt;Content Delivery Network (CDN)&lt;/strong&gt; helps — it places cached copies at locations closer to users.&lt;/p&gt;

&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%2F75wik7dirug0eafbfftv.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%2F75wik7dirug0eafbfftv.png" alt="A CDN moves the copy closer to the user: a user in Tokyo reaches the nearest edge, which only contacts the origin server when it doesn't already have the content" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A user in Tokyo doesn't necessarily need to travel all the way to your origin server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User → Tokyo edge → Response

instead of

User → Tokyo → Internet → Origin → Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not just a latency optimisation. It can also dramatically reduce traffic hitting your origin infrastructure.&lt;/p&gt;

&lt;p&gt;What commonly belongs in a CDN: images, JavaScript, CSS, fonts, videos, static files, and sometimes API responses.&lt;/p&gt;

&lt;p&gt;But here's another important lesson: &lt;strong&gt;not everything should be cached at the CDN.&lt;/strong&gt; Highly personalised or sensitive data requires much more careful handling.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Application cache: keep data close to your code
&lt;/h2&gt;

&lt;p&gt;Now let's move inside the application.&lt;/p&gt;

&lt;p&gt;Suppose your application frequently needs this information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;US → United States
IN → India
CA → Canada
UK → United Kingdom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Does it make sense to query a database every time? Probably not. You could keep this in memory instead, where retrieval is extremely quick. There is no database call. There isn't even a network call.&lt;/p&gt;

&lt;p&gt;This is sometimes called a &lt;strong&gt;local cache&lt;/strong&gt; or &lt;strong&gt;in-process cache&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local cache:          Distributed cache:

Application           Application
    ↓                     ↓
  Memory              Network
    ↓                     ↓
  Result              Distributed cache
                          ↓
                      Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The local cache wins on raw latency. But now we have a problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The problem with local caches
&lt;/h2&gt;

&lt;p&gt;Imagine you have three application servers, each with its own cache. All three are holding &lt;code&gt;product:123&lt;/code&gt; at &lt;code&gt;$999&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now someone changes the product price to &lt;code&gt;$899&lt;/code&gt;. The instance that handled the write updates its own copy. The other two don't automatically know anything happened.&lt;/p&gt;

&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%2F6cx9wyec0txctql71hxi.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%2F6cx9wyec0txctql71hxi.png" alt="Per-instance caches vs. one shared cache: with a cache per instance the same key returns different values across servers, while a shared cache holds one copy at the cost of a network call" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The cache is fast. But the copies aren't necessarily consistent.&lt;/p&gt;

&lt;p&gt;This is one of the fundamental trade-offs in caching:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The closer the cache is to the application, the faster it can be — but the harder multiple copies can be to keep consistent.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5. Distributed cache: one shared cache
&lt;/h2&gt;

&lt;p&gt;Instead of giving every application instance its own cache, we can introduce a shared one, as shown on the right side of the diagram above. All three application servers now read the same cached data, and the divergence problem goes away.&lt;/p&gt;

&lt;p&gt;But we have introduced another trade-off. The cache is no longer inside the application, 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;App → Memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;App → Network → Cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still much faster than many database operations — but not free. And now the cache itself needs monitoring, scaling, failover, capacity planning, security, backup and recovery considerations, and operational ownership.&lt;/p&gt;

&lt;p&gt;We're starting to see an important pattern. &lt;strong&gt;Caching isn't free. It moves cost around.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Database caching
&lt;/h2&gt;

&lt;p&gt;Here's something many developers forget: &lt;strong&gt;the database itself is already using caches.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Databases typically keep frequently accessed data, indexes, pages, and other structures in memory. So when your application executes:&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&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;the database may not need to read from physical storage at all. It may already have what it needs in memory.&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
     │
     ├── Memory cache → HIT
     │
     └── Disk         → MISS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters because it means adding an application cache isn't automatically the first thing you should do. Sometimes the database is already performing well. Sometimes a missing index, an inefficient query, or an excessive number of round trips is the real problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before adding a cache, understand the bottleneck.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Putting the layers together
&lt;/h2&gt;

&lt;p&gt;Now let's look at a realistic request. A user opens a product page, and the request may encounter several layers before anything answers it.&lt;/p&gt;

&lt;p&gt;What makes layered caching powerful is that different requests stop at different places:&lt;/p&gt;

&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%2Fee2qkmd4byfn8vrbwtqz.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%2Fee2qkmd4byfn8vrbwtqz.png" alt="Where a request stops: a returning visitor is answered by the browser, a nearby user is answered by the CDN, and only a first request for a key travels through every layer to the database" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Only when everything misses do we reach the database — and then the response travels back up, populating the layers on its way, so the next request stops sooner.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. But should we add every layer?
&lt;/h2&gt;

&lt;p&gt;Absolutely not. This is where caching becomes dangerous.&lt;/p&gt;

&lt;p&gt;It is tempting to think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser + CDN + Local cache + Redis + Database cache = maximum performance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not necessarily. Every layer introduces complexity. Imagine a value that exists in the browser, the CDN, a local cache, Redis, and the database. Now ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which one is correct?&lt;/li&gt;
&lt;li&gt;If the value changes, which layers need to be invalidated?&lt;/li&gt;
&lt;li&gt;If Redis goes down, does the application continue?&lt;/li&gt;
&lt;li&gt;If the CDN serves stale content, is that acceptable?&lt;/li&gt;
&lt;li&gt;If the local cache holds an older value, how do we update it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suddenly our simple performance optimisation has become a distributed-systems problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. So where should you put the cache?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cache location&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;Main trade-off&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Browser&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;User-specific and static resources&lt;/td&gt;
&lt;td&gt;Limited server control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CDN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Global, static content&lt;/td&gt;
&lt;td&gt;Invalidation and personalisation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Local application memory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tiny, frequently used data&lt;/td&gt;
&lt;td&gt;Multiple copies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Distributed cache&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Shared application data&lt;/td&gt;
&lt;td&gt;Network and infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database cache&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Database internals&lt;/td&gt;
&lt;td&gt;Doesn't eliminate DB requests&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There isn't one universally correct location. The right answer depends on what you're caching.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. A simple decision framework
&lt;/h2&gt;

&lt;p&gt;Before adding a cache, ask four questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who needs this data?&lt;/strong&gt;&lt;br&gt;
Only one application instance? A local cache might work. Every instance? Consider a distributed cache. Every user? A CDN or browser cache may be appropriate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How frequently does it change?&lt;/strong&gt;&lt;br&gt;
Rarely — longer TTLs become possible. Frequently — you need a stronger invalidation strategy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How expensive is the original operation?&lt;/strong&gt;&lt;br&gt;
If the database query takes 2 ms, caching may not be worth much. If it takes 500 ms and happens thousands of times per second, caching becomes very interesting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much stale data can we tolerate?&lt;/strong&gt;&lt;br&gt;
This is perhaps the most important question. A few minutes of staleness is probably fine for a profile picture or a product description. Inventory counts are a harder call. A bank balance has very different requirements altogether.&lt;/p&gt;

&lt;p&gt;The answer to that last question determines the caching strategy more than any benchmark will.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bigger lesson
&lt;/h2&gt;

&lt;p&gt;Caching isn't just about where to store data. It's about deciding:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How far are we willing to move away from the source of truth in exchange for speed?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The closer the cache is to the user, the faster the response can become. But the farther we move from the source of truth, the more carefully we need to think about freshness, invalidation, consistency, failures, observability, and operational complexity.&lt;/p&gt;

&lt;p&gt;That's why experienced engineers don't simply ask &lt;em&gt;"should we use Redis?"&lt;/em&gt; They ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What problem are we trying to solve, and which caching layer solves it with the least complexity?"&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;So far we've covered why caching exists, how caching works, and where caching lives.&lt;/p&gt;

&lt;p&gt;But we still haven't answered a critical question: &lt;strong&gt;when the application reads or writes data, exactly how should the cache participate?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Should the application talk to the cache first? Should the cache automatically load missing data? Should writes go to the cache first? Should the cache write to the database later?&lt;/p&gt;

&lt;p&gt;These are &lt;strong&gt;caching patterns&lt;/strong&gt;, and choosing the wrong one can create subtle production problems. That's where we'll go next.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;How many caching layers does your production system actually have — and could you name what invalidates each one? I'd genuinely like to know how many people can answer that second part.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>caching</category>
      <category>systemdesign</category>
      <category>backend</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How Caching Actually Works</title>
      <dc:creator>Muralidharan Lakshmanan</dc:creator>
      <pubDate>Thu, 13 Aug 2026 01:58:42 +0000</pubDate>
      <link>https://dev.to/muralidharan_lakshmanan/how-caching-actually-works-309f</link>
      <guid>https://dev.to/muralidharan_lakshmanan/how-caching-actually-works-309f</guid>
      <description>&lt;p&gt;Previously in this series, we looked at &lt;em&gt;why&lt;/em&gt; caching exists. It can reduce latency, lower database load, and help an application handle more traffic.&lt;/p&gt;

&lt;p&gt;But there is a natural next question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What actually happens inside a cache?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To answer that, we need to understand where the data lives, how we find it, how long it stays there, what happens when the cache fills up, and why location matters.&lt;/p&gt;

&lt;p&gt;Once these concepts click, technologies such as Redis, Memcached, browser caches, CDNs, and application-level caches become much easier to reason about.&lt;/p&gt;




&lt;h2&gt;
  
  
  Memory vs. disk: why is a cache so fast?
&lt;/h2&gt;

&lt;p&gt;When people say a cache is fast, they are often talking about memory — specifically RAM.&lt;/p&gt;

&lt;p&gt;A database may use memory too. Modern databases aggressively cache data in RAM. The point is not that databases are always slow or that caches are always in RAM. The point is that a purpose-built cache often keeps frequently used data and the lookup path extremely simple.&lt;/p&gt;

&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%2Fh46p38xsxv2cafwdehq7.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%2Fh46p38xsxv2cafwdehq7.png" alt="Memory vs. disk: RAM offers fast access with limited, usually volatile capacity and lower latency; disk offers larger durable capacity with higher latency" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Memory is fast, but it is limited and usually treated as less durable than disk storage. Disk gives us much more capacity and persistence, but accessing data from disk or through a more complex storage path generally costs more time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cache     →  optimise for fast access
Database  →  optimise for durable, structured, reliable storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a mental model, not an absolute rule. Modern systems blur the boundary: databases use RAM caches, operating systems cache disk pages, and some caches can persist data.&lt;/p&gt;




&lt;h2&gt;
  
  
  The key-value model
&lt;/h2&gt;

&lt;p&gt;Most caching systems become easier to understand when you think of them as a giant dictionary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;key:    "user:123"
value:  { "name": "John", "country": "USA" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application knows the key. The cache uses that key to find the value quickly.&lt;/p&gt;

&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%2Fwcr1le4w5ugrc31474dz.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%2Fwcr1le4w5ugrc31474dz.png" alt="The key-value model: the application asks for a key it already knows, and the cache returns the matching value in a single lookup with no scan and no joins" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Good cache keys are predictable and unique enough to avoid collisions:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Key design becomes increasingly important as systems grow. Poorly designed keys can cause collisions, make invalidation difficult, or create unexpected hot spots.&lt;/p&gt;




&lt;h2&gt;
  
  
  TTL: how long should cached data live?
&lt;/h2&gt;

&lt;p&gt;A cache cannot keep every value forever. At some point, cached data needs to expire. That is where &lt;strong&gt;TTL — Time To Live&lt;/strong&gt; — comes in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;weather:atlanta  →  { ... }   TTL = 5 minutes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the next five minutes, requests can use the cached value. After the TTL expires, the entry is considered expired and the application may need to fetch fresh data.&lt;/p&gt;

&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%2Fr26gky49kk4hmecxfjaa.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%2Fr26gky49kk4hmecxfjaa.png" alt="TTL timeline: requests within the TTL window are served from the cache as hits; once the TTL expires, the next request is a miss that refetches and stores the value again" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TTL is a trade-off. A very short TTL keeps data fresh but causes more cache misses. A very long TTL improves cache efficiency but increases the chance of serving stale information.&lt;/p&gt;

&lt;p&gt;There is no magic TTL. A stock price may need a very different freshness strategy from a country-code lookup table. A product description may tolerate minutes or hours of staleness, while an account balance may require much stricter rules.&lt;/p&gt;

&lt;p&gt;The right TTL comes from the business requirement — not from a universal caching rule.&lt;/p&gt;




&lt;h2&gt;
  
  
  What happens when the cache is full?
&lt;/h2&gt;

&lt;p&gt;Imagine your cache has a fixed amount of memory and every new item wants a place. Eventually, there is no room.&lt;/p&gt;

&lt;p&gt;The cache needs a way to decide which existing item should be removed. This is called an &lt;strong&gt;eviction strategy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Think of it like a small desk. When the desk is full and a new document arrives, you need a rule for deciding which old document gets thrown away.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Policy&lt;/th&gt;
&lt;th&gt;Full name&lt;/th&gt;
&lt;th&gt;What it looks at&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LRU&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Least Recently Used&lt;/td&gt;
&lt;td&gt;Recency — how long since this was last touched&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LFU&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Least Frequently Used&lt;/td&gt;
&lt;td&gt;Frequency — how often this has been accessed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FIFO&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;First In, First Out&lt;/td&gt;
&lt;td&gt;Insertion order — how long this has been here&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The distinction sounds academic until you apply all three to the same cache at the same moment. Consider a cache that is full at three entries when a fourth item arrives:&lt;/p&gt;

&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%2Fx65yyc71hkqyfl21htba.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%2Fx65yyc71hkqyfl21htba.png" alt="Eviction comparison: for the same three cached entries, FIFO evicts the oldest insertion, LRU evicts the least recently used entry, and LFU evicts the least frequently used entry" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same data, same instant, three different victims. FIFO throws out the entry that has been accessed the most, because it happened to arrive first. LFU throws out the entry that was just used a minute ago, because it hasn't accumulated many accesses yet.&lt;/p&gt;

&lt;p&gt;Neither is a bug. Each policy encodes an assumption about which data you are likely to need next, and that assumption is either a good fit for your access pattern or a bad one. Real systems may offer additional policies or combinations.&lt;/p&gt;

&lt;p&gt;The important principle is this: when capacity is limited, the cache needs a policy for deciding what stays.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cache locality: keep data close
&lt;/h2&gt;

&lt;p&gt;Another important idea is &lt;strong&gt;locality&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine an application server in Atlanta accessing a nearby cache versus repeatedly making requests to a cache across the country. Both may work, but the network path matters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application  →  nearby cache   →  response
Application  →  distant cache  →  network  →  response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every network hop introduces latency and another possible failure point.&lt;/p&gt;

&lt;p&gt;This is why system designers often try to keep frequently accessed data physically or logically close to the application that needs it. The same idea appears at many layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CPU caches&lt;/strong&gt; keep data close to the processor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser caches&lt;/strong&gt; keep content close to the user&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDNs&lt;/strong&gt; keep content close to geographic regions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application caches&lt;/strong&gt; keep frequently used data close to the application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Locality is not only geography. It can also mean reducing unnecessary network calls and expensive processing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding the performance numbers
&lt;/h2&gt;

&lt;p&gt;Saying "the cache is fast" is not enough. You need to understand what &lt;em&gt;fast&lt;/em&gt; actually means.&lt;/p&gt;

&lt;h3&gt;
  
  
  Latency
&lt;/h3&gt;

&lt;p&gt;Latency measures how long an individual operation takes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cache lookup:    2 ms
Database query:  80 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are illustrative numbers, not universal benchmarks. Real measurements depend on hardware, network distance, workload, serialization, concurrency, and many other factors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Throughput
&lt;/h3&gt;

&lt;p&gt;Throughput is how much work the system can handle over time — for example, requests per second.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5,000 cache operations/sec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A cache with low latency but insufficient throughput can still become a bottleneck.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache hit ratio
&lt;/h3&gt;

&lt;p&gt;The hit ratio tells us how often requested data is found in 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;1,000 requests
  900 hits
  100 misses

Hit ratio = 90%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A high hit ratio is generally useful because more requests avoid the backend. But it is not a universal score of success.&lt;/p&gt;

&lt;p&gt;Imagine a cache with a 99% hit ratio where each cache lookup is expensive, or where the 1% of misses trigger extremely costly database operations. The hit ratio alone does not tell the whole story.&lt;/p&gt;

&lt;h3&gt;
  
  
  Look at the numbers together
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;latency + throughput + hit ratio + memory usage + backend load
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, after introducing a cache, API latency might drop from 150 ms to 40 ms and database CPU might fall significantly — but cache memory could be growing too quickly. That is not simply success or failure; it is a signal for the next design decision.&lt;/p&gt;




&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;Let's walk through one request:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User requests product 987&lt;/li&gt;
&lt;li&gt;Application builds the key: &lt;code&gt;product:987&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Cache checks the key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hit?&lt;/strong&gt; Return the cached value&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Miss?&lt;/strong&gt; Query the database&lt;/li&gt;
&lt;li&gt;Store the result in the cache&lt;/li&gt;
&lt;li&gt;Apply a TTL&lt;/li&gt;
&lt;li&gt;If the cache becomes full, an eviction policy decides what leaves&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Caching is no longer just "put data somewhere fast." It is a small system with rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where is the data?&lt;/li&gt;
&lt;li&gt;How do I find it?&lt;/li&gt;
&lt;li&gt;How long does it live?&lt;/li&gt;
&lt;li&gt;What happens when it expires?&lt;/li&gt;
&lt;li&gt;What happens when there is no space?&lt;/li&gt;
&lt;li&gt;How fast is the lookup?&lt;/li&gt;
&lt;li&gt;What happens when the cache fails?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those questions are the foundation of good cache design.&lt;/p&gt;




&lt;h2&gt;
  
  
  Caching is a layer, not just a storage box
&lt;/h2&gt;

&lt;p&gt;One useful mental model is to think of caching as a layer between the application and the source of truth.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
  ↓
Application
  ↓
Cache      ← fast, temporary copy
  ↓
Database   ← durable source of truth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cache exists to reduce the cost of repeatedly reaching the source of truth.&lt;/p&gt;

&lt;p&gt;That distinction becomes very important when we start discussing consistency, invalidation, and failure handling.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;So far, we have looked at what caching solves and how a basic cache works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The next question is architectural: where should the cache live?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Should it live inside the application? On a separate server? At the edge? In the browser? In front of a database?&lt;/p&gt;

&lt;p&gt;Next in this series, we'll explore the different places caching can exist — from browser caches and CDNs to application and distributed caches — and understand why choosing the right layer can be just as important as choosing the caching technology.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Which eviction policy does your cache actually use — and do you know why that one? I'd be curious to hear in the comments whether it was a deliberate choice or the default that shipped with the tool.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>caching</category>
      <category>systemdesign</category>
      <category>backend</category>
      <category>performance</category>
    </item>
    <item>
      <title>Caching Is Simple... Until It Isn't</title>
      <dc:creator>Muralidharan Lakshmanan</dc:creator>
      <pubDate>Wed, 12 Aug 2026 01:55:57 +0000</pubDate>
      <link>https://dev.to/muralidharan_lakshmanan/caching-is-simple-until-it-isnt-2h78</link>
      <guid>https://dev.to/muralidharan_lakshmanan/caching-is-simple-until-it-isnt-2h78</guid>
      <description>&lt;p&gt;If you have worked on software systems for a while, you have probably heard this advice:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Just add a cache."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It sounds simple.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your application is slow? Add a cache.&lt;/li&gt;
&lt;li&gt;Your database is getting too many requests? Add a cache.&lt;/li&gt;
&lt;li&gt;Your API needs to handle more users? Add a cache.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And surprisingly often, it works.&lt;/p&gt;

&lt;p&gt;But caching is one of those things that looks incredibly simple when you first learn it — and becomes much more interesting once you use it in a real production system.&lt;/p&gt;

&lt;p&gt;Because the moment you add a cache, you are no longer dealing with just one question: &lt;em&gt;"How can I make this faster?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You also have to think about what to cache, how long to keep it, what happens when the data changes, what happens when the cache is unavailable, how much memory it will use, and what happens when thousands of users request the same uncached data.&lt;/p&gt;

&lt;p&gt;That's when caching stops being simple.&lt;/p&gt;




&lt;h2&gt;
  
  
  What problem does caching actually solve?
&lt;/h2&gt;

&lt;p&gt;Imagine you have an application that needs to display a user's profile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User  →  Application  →  Database  →  Application  →  User
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application receives the request, queries the database, gets the data, and sends it back.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /users/123
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&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;Nothing is wrong with this. But what happens if 10,000 requests ask for the same information?&lt;/p&gt;

&lt;p&gt;You could end up executing thousands of database queries for data that hasn't changed.&lt;/p&gt;

&lt;p&gt;That's where caching comes in. Instead of asking the database every time, we keep frequently requested data in a faster storage layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User  →  Application  →  Cache  →  Database (only when necessary)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the data is already in the cache, we don't need to contact the database. That is the basic idea.&lt;/p&gt;

&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%2Fdkh6u1s835hndff54m6l.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%2Fdkh6u1s835hndff54m6l.png" alt=" " width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A real-world analogy
&lt;/h2&gt;

&lt;p&gt;Think about a restaurant. Imagine you are a waiter and customers frequently ask for the restaurant's menu.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approach 1: Go to the kitchen every time.&lt;/strong&gt;&lt;br&gt;
Every time someone asks, "Can I see the menu?", you walk to the kitchen, find the menu, bring it back, and hand it over. Now imagine doing that 500 times. It would be ridiculous.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approach 2: Keep the menu at the table.&lt;/strong&gt;&lt;br&gt;
Instead, you keep a copy of the menu on every table. When someone asks for it, you simply point to the menu already sitting there. Much faster.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;In the restaurant&lt;/th&gt;
&lt;th&gt;In your system&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The customer&lt;/td&gt;
&lt;td&gt;The user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The waiter&lt;/td&gt;
&lt;td&gt;The application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The kitchen&lt;/td&gt;
&lt;td&gt;The database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The menu on the table&lt;/td&gt;
&lt;td&gt;The cache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;We keep frequently needed information closer to where it is needed.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Latency: why does caching make things faster?
&lt;/h2&gt;

&lt;p&gt;One of the biggest reasons to use caching is latency — the time it takes for something to respond.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Example latency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cache request&lt;/td&gt;
&lt;td&gt;~2 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database request&lt;/td&gt;
&lt;td&gt;~80 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The numbers above are illustrative, not universal benchmarks. The important point is that an in-memory cache can often respond much faster than a database path that involves network communication, query processing, indexes, joins, disk I/O, connection management, and other work.&lt;/p&gt;

&lt;p&gt;When you multiply that difference by thousands or millions of requests, the impact becomes significant.&lt;/p&gt;


&lt;h2&gt;
  
  
  Caching also reduces database load
&lt;/h2&gt;

&lt;p&gt;Speed is only part of the story. Caching can also protect your database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Without caching:  100,000 requests  →  potentially 100,000 database queries
With caching:     100,000 requests  →  cache  →  perhaps only a handful of queries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine an e-commerce site where a popular product is requested thousands of times. Product name, description, specifications, and other relatively stable information may be good candidates for caching.&lt;/p&gt;

&lt;p&gt;The database gets fewer requests. The application gets faster responses. The system can potentially handle more traffic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cache hit vs. cache miss
&lt;/h2&gt;

&lt;p&gt;Two terms appear everywhere when talking about caching: &lt;strong&gt;cache hit&lt;/strong&gt; and &lt;strong&gt;cache miss&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache hit
&lt;/h3&gt;

&lt;p&gt;A cache hit happens when the data you are looking for is already in 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;Application  →  Cache  →  "Found it!"  →  Return data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database is not needed for that request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache miss
&lt;/h3&gt;

&lt;p&gt;A cache miss happens when the requested data is not in 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;Application  →  Cache  →  "Not found"  →  Database  →  Cache  →  Return data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first request is slower, but the result can be stored in the cache so future requests can be faster.&lt;/p&gt;

&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%2Fo71mkupzc2em0ewxbcfq.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%2Fo71mkupzc2em0ewxbcfq.png" alt=" " width="799" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;cache-aside&lt;/strong&gt; pattern, and it's worth noticing who is in charge: the application checks the cache, the application falls back to the database, and the application writes the result back. The cache itself knows nothing about your database.&lt;/p&gt;




&lt;h2&gt;
  
  
  So why not cache everything?
&lt;/h2&gt;

&lt;p&gt;This is where things get interesting. If caching makes things faster, why not simply cache everything?&lt;/p&gt;

&lt;p&gt;Because caching has a cost. A cache requires memory, infrastructure, monitoring, maintenance, and additional application logic. And perhaps the biggest problem is &lt;strong&gt;stale data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine you cache this product:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"product"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Laptop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$999"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cache keeps it for 30 minutes. Five minutes later, the real price changes from &lt;code&gt;$999&lt;/code&gt; to &lt;code&gt;$899&lt;/code&gt;. The cache still contains &lt;code&gt;$999&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now the application may show the user the wrong price.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the classic caching problem: how do you keep cached data reasonably fresh?&lt;/p&gt;

&lt;p&gt;Possible approaches include short expiration times, explicit invalidation, event-driven updates, versioned cache keys, and other strategies. Each one introduces additional design decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Caching can actually make a system worse
&lt;/h2&gt;

&lt;p&gt;Caching isn't automatically good. A poorly designed cache can create new problems.&lt;/p&gt;

&lt;p&gt;For example, imagine your application caches millions of objects without considering memory usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cache memory → 100%  →  Evictions  →  Cache misses  →  More database requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or imagine the cache suddenly becomes unavailable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Normal:   10,000 req/sec  →  Cache  →  Database sees a manageable load
Failure:  10,000 req/sec  →  Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the database is suddenly exposed to traffic that the cache normally absorbs, the database can become the next bottleneck.&lt;/p&gt;

&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%2Ftfs2mg4xmtvdzi7fciib.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%2Ftfs2mg4xmtvdzi7fciib.png" alt=" " width="799" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Other problems include cache stampede, hot keys, invalidation issues, inconsistent data, memory pressure, network latency, cache availability, and serialization/deserialization overhead.&lt;/p&gt;

&lt;p&gt;We'll explore these problems later in the series.&lt;/p&gt;




&lt;h2&gt;
  
  
  The important question isn't "should I use caching?"
&lt;/h2&gt;

&lt;p&gt;A better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What should I cache, why should I cache it, and what happens when the cache is wrong or unavailable?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That shift in thinking is important.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Often good candidates:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frequently requested data&lt;/li&gt;
&lt;li&gt;Relatively stable data&lt;/li&gt;
&lt;li&gt;Expensive database queries&lt;/li&gt;
&lt;li&gt;Expensive computations&lt;/li&gt;
&lt;li&gt;External API responses&lt;/li&gt;
&lt;li&gt;Configuration or reference data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Often poor candidates:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data that changes constantly&lt;/li&gt;
&lt;li&gt;Data that requires strict real-time consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That second list isn't a hard "never" — it may just require a more carefully designed caching strategy. There is no universal answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Caching is a trade-off
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;↔ Complexity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Freshness&lt;/td&gt;
&lt;td&gt;↔ Speed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory cost&lt;/td&gt;
&lt;td&gt;↔ Database cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Availability&lt;/td&gt;
&lt;td&gt;↔ Consistency&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is the real story of caching. A cache can make a system faster and reduce database pressure, but it also introduces another component and another set of failure modes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And that's why caching is simple... until it isn't.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Next in this series, we'll go one level deeper and look at how caching actually works: where cached data lives, memory vs. disk, TTL (time to live), cache keys, eviction strategies such as LRU and LFU, and what happens when a cache runs out of memory.&lt;/p&gt;

&lt;p&gt;Once you understand those fundamentals, technologies such as Redis and Memcached become much easier to understand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because before learning which caching technology to use, it's worth understanding what problem we're actually trying to solve.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's the worst caching bug you've shipped? I'm collecting war stories for a later post in this series — drop yours in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>caching</category>
      <category>systemdesign</category>
      <category>backend</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
