<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[DevOps IN SPACE: Engineering]]></title><description><![CDATA[This contains a collection of engineering topics]]></description><link>https://devopsguyankit.substack.com/s/engineering</link><image><url>https://substackcdn.com/image/fetch/$s_!K2kC!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9aa9f505-ddb8-42b5-aaf3-f25fb8290953_271x271.png</url><title>DevOps IN SPACE: Engineering</title><link>https://devopsguyankit.substack.com/s/engineering</link></image><generator>Substack</generator><lastBuildDate>Thu, 18 Jun 2026 11:41:24 GMT</lastBuildDate><atom:link href="https://devopsguyankit.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Ankit Ranjan]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[devopsguyankit@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[devopsguyankit@substack.com]]></itunes:email><itunes:name><![CDATA[Ankit Ranjan]]></itunes:name></itunes:owner><itunes:author><![CDATA[Ankit Ranjan]]></itunes:author><googleplay:owner><![CDATA[devopsguyankit@substack.com]]></googleplay:owner><googleplay:email><![CDATA[devopsguyankit@substack.com]]></googleplay:email><googleplay:author><![CDATA[Ankit Ranjan]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Caching Demystified: From Theory to Production]]></title><description><![CDATA[Cache hits, Cache misses, update strategis, invalidation nightmares - everything you need to build fast, consistent, production grade caching systems.]]></description><link>https://devopsguyankit.substack.com/p/caching-demystified-from-theory-to</link><guid isPermaLink="false">https://devopsguyankit.substack.com/p/caching-demystified-from-theory-to</guid><dc:creator><![CDATA[Ankit Ranjan]]></dc:creator><pubDate>Wed, 13 May 2026 06:04:03 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!O1JG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dff3c21-e44c-455c-845a-3e357ab5b5b5_1112x515.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There is an old joke in distributed systems: <em>&#8220;There are only two hard things in computer science &#8212; cache invalidation and naming things.&#8221;</em> Phil Karlton said it decades ago, and every engineer who has chased a stale-cache bug at 2 a.m. has silently agreed.</p><p>But caching is also one of the highest-leverage moves in a backend engineer&#8217;s toolkit. Done right, it can reduce database load by 80%, cut median latency from 120 ms to 4 ms, and let a $20/month VM serve traffic that would otherwise require a $2,000 database cluster.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://devopsguyankit.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading DevOps IN SPACE! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!U9dl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f336266-171b-4da6-a9d3-3ec40d2f2b46_1112x187.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!U9dl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f336266-171b-4da6-a9d3-3ec40d2f2b46_1112x187.png 424w, https://substackcdn.com/image/fetch/$s_!U9dl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f336266-171b-4da6-a9d3-3ec40d2f2b46_1112x187.png 848w, https://substackcdn.com/image/fetch/$s_!U9dl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f336266-171b-4da6-a9d3-3ec40d2f2b46_1112x187.png 1272w, https://substackcdn.com/image/fetch/$s_!U9dl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f336266-171b-4da6-a9d3-3ec40d2f2b46_1112x187.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!U9dl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f336266-171b-4da6-a9d3-3ec40d2f2b46_1112x187.png" width="1112" height="187" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3f336266-171b-4da6-a9d3-3ec40d2f2b46_1112x187.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:187,&quot;width&quot;:1112,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:27644,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://devopsguyankit.substack.com/i/197450839?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f336266-171b-4da6-a9d3-3ec40d2f2b46_1112x187.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!U9dl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f336266-171b-4da6-a9d3-3ec40d2f2b46_1112x187.png 424w, https://substackcdn.com/image/fetch/$s_!U9dl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f336266-171b-4da6-a9d3-3ec40d2f2b46_1112x187.png 848w, https://substackcdn.com/image/fetch/$s_!U9dl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f336266-171b-4da6-a9d3-3ec40d2f2b46_1112x187.png 1272w, https://substackcdn.com/image/fetch/$s_!U9dl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f336266-171b-4da6-a9d3-3ec40d2f2b46_1112x187.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><h2>What exactly is a cache?</h2><p>A cache is a <strong>fast, temporary store</strong> that sits between your application and a slower data source, typically a database, an external API, or a compute-heavy function. When you ask for a piece of data, you check the cache first. If it&#8217;s there (<em>cache hit</em>), you return it immediately. If it&#8217;s not (<em>cache miss</em>), you fetch it from the source, store it in the cache, and return it.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!O1JG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dff3c21-e44c-455c-845a-3e357ab5b5b5_1112x515.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!O1JG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dff3c21-e44c-455c-845a-3e357ab5b5b5_1112x515.png 424w, https://substackcdn.com/image/fetch/$s_!O1JG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dff3c21-e44c-455c-845a-3e357ab5b5b5_1112x515.png 848w, https://substackcdn.com/image/fetch/$s_!O1JG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dff3c21-e44c-455c-845a-3e357ab5b5b5_1112x515.png 1272w, https://substackcdn.com/image/fetch/$s_!O1JG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dff3c21-e44c-455c-845a-3e357ab5b5b5_1112x515.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!O1JG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dff3c21-e44c-455c-845a-3e357ab5b5b5_1112x515.png" width="1112" height="515" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0dff3c21-e44c-455c-845a-3e357ab5b5b5_1112x515.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:515,&quot;width&quot;:1112,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:50488,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://devopsguyankit.substack.com/i/197450839?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dff3c21-e44c-455c-845a-3e357ab5b5b5_1112x515.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!O1JG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dff3c21-e44c-455c-845a-3e357ab5b5b5_1112x515.png 424w, https://substackcdn.com/image/fetch/$s_!O1JG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dff3c21-e44c-455c-845a-3e357ab5b5b5_1112x515.png 848w, https://substackcdn.com/image/fetch/$s_!O1JG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dff3c21-e44c-455c-845a-3e357ab5b5b5_1112x515.png 1272w, https://substackcdn.com/image/fetch/$s_!O1JG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dff3c21-e44c-455c-845a-3e357ab5b5b5_1112x515.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="pullquote"><p>Figure 1 &#8212; Cache hit &amp; miss flow. A hit returns in ~1 ms; a miss incurs the full database round-trip plus a write-through to populate the cache.</p></div><h2>Checking the cache &#8212; the lookup pattern</h2><p>Every cache interaction starts with a lookup. The lookup must be <strong>fast, non-blocking, and atomic</strong>.</p><p> Here&#8217;s the canonical pattern in Node.js with Redis, annotated for production:</p><pre><code><em>// cache.service.ts - production-grade cache client</em>
import { Redis } from &#8216;ioredis&#8217;;

const redis = new Redis
(
{
  host: process.env.REDIS_HOST,
  port: 6379,
  maxRetriesPerRequest: 3,
  enableReadyCheck: true,
  lazyConnect: true,
  connectTimeout: 5000,
}
);

async function getCached&lt;T&gt;
(
  key: string,
  fetchFn: () =&gt; Promise&lt;T&gt;,
  ttlSeconds: number = 300
): Promise&lt;T&gt; 
{

  <em>// 1. Try the cache first (O(1) lookup)</em>
  const cached = await redis.get(key);
  if (cached !== null) 
{
    recordMetric(&#8217;cache.hit&#8217;, { key });
    return JSON.parse(cached) as T;
  }

  <em>// 2. Cache miss &#8212; fetch from source</em>
  recordMetric(&#8217;cache.miss&#8217;, { key });
  const data = await fetchFn();

  <em>// 3. Populate cache asynchronously (don&#8217;t block the response)</em>
  redis
    .setex(key, ttlSeconds, JSON.stringify(data))
    .catch((err) =&gt; console.error(&#8217;Cache write failed:&#8217;, err));

  return data;
}

<em>// Usage</em>
const user = await getCached(
  `user:${userId}`,
  () =&gt; db.findUserById(userId),
  600 <em>// 10 minutes</em>
);</code></pre><blockquote><p>Note: Key naming convention matters. Use a hierarchical namespace like <code>entity:id:field</code> &#8212; e.g. <code>user:42:profile</code>. This lets you use <code>SCAN</code> patterns to invalidate entire entity groups without a full flush. Avoid generic keys like <code>data</code> or <code>result</code>.</p></blockquote><h3>Checking cache existence (without fetching value)</h3><p>Sometimes you only need to know <em>whether</em> a key exists. For example, to check if a rate-limit counter is set, or if a job is already queued. Use <code>EXISTS</code> instead of <code>GET</code>: it returns 0 or 1 and costs less bandwidth.</p><pre><code>import redis

r = redis.Redis(host=&#8217;localhost&#8217;, port=6379, decode_responses=True)

<em># Check existence without pulling the value</em>
def is_rate_limited(user_id: str) -&gt; bool:
    key = f&#8221;ratelimit:{user_id}&#8221;
    count = r.get(key)
    if count is None:
        <em># First request &#8212; set counter with 60s window</em>
        r.setex(key, 60, 1)
        return False
    if int(count) &gt;= 100:
        return True
    <em># Atomic increment &#8212; safe under concurrent requests</em>
    r.incr(key)
    return False

<em># Check TTL remaining (useful for debugging)</em>
ttl = r.ttl(f&#8221;user:{user_id}:profile&#8221;)
<em># -2 = key doesn&#8217;t exist, -1 = no expiry, N = seconds remaining</em></code></pre><h2>Cache update strategies</h2><p>This is where most engineers underestimate complexity. <em>How</em> you update the cache after data changes determines your consistency guarantees, your failure modes, and your operational burden. There are four mainstream patterns.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QP1V!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1484c7c3-1a49-45ad-99ad-ec9bec79380f_1112x578.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QP1V!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1484c7c3-1a49-45ad-99ad-ec9bec79380f_1112x578.png 424w, https://substackcdn.com/image/fetch/$s_!QP1V!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1484c7c3-1a49-45ad-99ad-ec9bec79380f_1112x578.png 848w, https://substackcdn.com/image/fetch/$s_!QP1V!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1484c7c3-1a49-45ad-99ad-ec9bec79380f_1112x578.png 1272w, https://substackcdn.com/image/fetch/$s_!QP1V!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1484c7c3-1a49-45ad-99ad-ec9bec79380f_1112x578.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QP1V!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1484c7c3-1a49-45ad-99ad-ec9bec79380f_1112x578.png" width="1112" height="578" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1484c7c3-1a49-45ad-99ad-ec9bec79380f_1112x578.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:578,&quot;width&quot;:1112,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:109049,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://devopsguyankit.substack.com/i/197450839?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1484c7c3-1a49-45ad-99ad-ec9bec79380f_1112x578.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QP1V!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1484c7c3-1a49-45ad-99ad-ec9bec79380f_1112x578.png 424w, https://substackcdn.com/image/fetch/$s_!QP1V!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1484c7c3-1a49-45ad-99ad-ec9bec79380f_1112x578.png 848w, https://substackcdn.com/image/fetch/$s_!QP1V!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1484c7c3-1a49-45ad-99ad-ec9bec79380f_1112x578.png 1272w, https://substackcdn.com/image/fetch/$s_!QP1V!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1484c7c3-1a49-45ad-99ad-ec9bec79380f_1112x578.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="callout-block" data-callout="true"><p>Figure 2 &#8212; The four cache update strategies and recommended use cases.</p></div><h3>Strategy 1: Cache-aside (lazy loading) &#8212; the default</h3><p>The application is responsible for all cache interactions. On a miss, it fetches from the database and populates the cache. This is the most prevalent strategy because it only caches data that is actually requested.</p><pre><code><em>// ProductService with cache-aside + singleflight to prevent thundering herd</em>
package service

import
 (
  &#8220;context&#8221;
  &#8220;encoding/json&#8221;
  &#8220;fmt&#8221;
  &#8220;time&#8221;
  &#8220;golang.org/x/sync/singleflight&#8221;
)

type ProductService struct 
{
  cache  CacheClient
  db     DBClient
  group  singleflight.Group
}

func (s *ProductService) GetProduct(ctx context.Context, id string) (*Product, error) 
{
  key := fmt.Sprintf(&#8221;product:%s&#8221;, id)

  <em>// singleflight collapses concurrent misses into one DB call</em>
  v, err, _ := s.group.Do(key, func() (interface{}, error) 
{
    <em>// 1. Check cache</em>
    if raw, err := s.cache.Get(ctx, key); err == nil 
{
      var p Product
      json.Unmarshal([]byte(raw), &amp;p)
      return &amp;p, nil
    }
    <em>// 2. Cache miss &#8212; hit database</em>
    p, err := s.db.FindProduct(ctx, id)
    if err != nil { return nil, err }

    <em>// 3. Write to cache (jitter prevents thundering herd on expiry)</em>
    ttl := 5*time.Minute + jitter(30*time.Second)
    data, _ := json.Marshal(p)
    s.cache.Set(ctx, key, data, ttl)

    return p, nil
  })

  if err != nil { return nil, err }
  return v.(*Product), nil
}</code></pre><blockquote><p>Note: - When a popular cache key expires, hundreds of concurrent requests all miss simultaneously and hammer the database. The <code>singleflight</code> pattern collapses all concurrent requests for the same key into a single database call, sharing the result with all waiters. Always use it in high-traffic services.</p></blockquote><h3>Strategy 2: Write-through &#8212; synchronous consistency</h3><p>Every write updates both the cache and the database atomically. The write path is slower, but you never serve stale reads, ideal for financial balances, inventory counts, or user settings where correctness trumps latency.</p><pre><code>async function updateUserBalance
(
  userId: string,
  newBalance: number
): Promise&lt;void&gt; 
{
  <em>// Write-through: update DB and cache in a single logical operation</em>
  const pipeline = redis.pipeline();

  await db.transaction(async (trx) =&gt;
 {
    <em>// 1. Update the source of truth</em>
    await trx.query
(
      &#8216;UPDATE accounts SET balance = ? WHERE id = ?&#8217;,
      [newBalance, userId]
    );

    <em>// 2. Invalidate the stale key (prefer invalidate over overwrite</em>
    <em>//    so we don&#8217;t cache uncommitted data if the transaction rolls back)</em>
    pipeline.del(`user:${userId}:balance`);
  });

  <em>// Execute cache invalidation only after commit succeeds</em>
  await pipeline.exec();
}</code></pre><blockquote><p>Note: If the transaction rolls back, you'll have a cache entry with data that was never persisted. Always invalidate (or update) the cache only after a successful database commit.</p></blockquote><h3>Strategy 3: Write-back (write-behind) &#8212; maximum write throughput</h3><p>Writes go to the cache immediately; an asynchronous worker flushes dirty entries to the database in batches. Massively improves write throughput, at the cost of a window of potential data loss if the cache node fails before flushing.</p><pre><code>import asyncio, json, redis.asyncio as aioredis

r = aioredis.Redis(host=&#8217;localhost&#8217;)
DIRTY_SET = &#8220;dirty:cart:keys&#8221;

async def update_cart(user_id: str, item_id: str, qty: int):
    <em># 1. Write to cache immediately (fast &#8212; returns in &lt;1ms)</em>
    key = f&#8221;cart:{user_id}&#8221;
    await r.hset(key, item_id, qty)

    <em># 2. Mark key as dirty for the flush worker</em>
    await r.sadd(DIRTY_SET, key)

async def flush_worker():
    &#8220;&#8221;&#8220;Runs every 5 seconds; flushes dirty keys to Postgres.&#8221;&#8220;&#8221;
    while True:
        await asyncio.sleep(5)
        dirty_keys = await r.smembers(DIRTY_SET)
        for key in dirty_keys:
            cart_data = await r.hgetall(key)
            user_id = key.split(&#8217;:&#8217;)[1]
            await db_upsert_cart(user_id, cart_data)
            await r.srem(DIRTY_SET, key)</code></pre><h2>Cache invalidation </h2><blockquote><p><em>The cache is just a liar you trust until you catch it. Invalidation is how you stop the lies.</em></p></blockquote><p>There are three mechanisms for keeping cached data accurate: <strong>TTL-based expiry</strong>, <strong>explicit invalidation</strong>, and <strong>event-driven invalidation</strong>. Each has its place.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Xhjf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3335ac5-7021-451f-abb6-e9c95b9d0819_1112x441.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Xhjf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3335ac5-7021-451f-abb6-e9c95b9d0819_1112x441.png 424w, https://substackcdn.com/image/fetch/$s_!Xhjf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3335ac5-7021-451f-abb6-e9c95b9d0819_1112x441.png 848w, https://substackcdn.com/image/fetch/$s_!Xhjf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3335ac5-7021-451f-abb6-e9c95b9d0819_1112x441.png 1272w, https://substackcdn.com/image/fetch/$s_!Xhjf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3335ac5-7021-451f-abb6-e9c95b9d0819_1112x441.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Xhjf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3335ac5-7021-451f-abb6-e9c95b9d0819_1112x441.png" width="1112" height="441" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e3335ac5-7021-451f-abb6-e9c95b9d0819_1112x441.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:441,&quot;width&quot;:1112,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:71586,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://devopsguyankit.substack.com/i/197450839?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3335ac5-7021-451f-abb6-e9c95b9d0819_1112x441.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Xhjf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3335ac5-7021-451f-abb6-e9c95b9d0819_1112x441.png 424w, https://substackcdn.com/image/fetch/$s_!Xhjf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3335ac5-7021-451f-abb6-e9c95b9d0819_1112x441.png 848w, https://substackcdn.com/image/fetch/$s_!Xhjf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3335ac5-7021-451f-abb6-e9c95b9d0819_1112x441.png 1272w, https://substackcdn.com/image/fetch/$s_!Xhjf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3335ac5-7021-451f-abb6-e9c95b9d0819_1112x441.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="callout-block" data-callout="true"><p>Figure 3 &#8212; Cache invalidation strategies. Most production systems combine TTL (safety net) with explicit invalidation (correctness).</p></div><h3>Event-driven invalidation with CDC (Change Data Capture)</h3><p>The most robust approach for microservice architectures: Debezium tails the Postgres WAL, publishes change events to Kafka, and a lightweight consumer deletes the relevant cache keys.</p><pre><code>from kafka import KafkaConsumer
import json, redis

r = redis.Redis(host=&#8217;redis&#8217;)

consumer = KafkaConsumer
(
    &#8216;postgres.public.users&#8217;,  <em># Debezium topic pattern</em>
    bootstrap_servers=[&#8217;kafka:9092&#8217;],
    group_id=&#8217;cache-invalidator&#8217;,
    auto_offset_reset=&#8217;latest&#8217;,
    value_deserializer=lambda v: json.loads(v)
)

for msg in consumer:
    event = msg.value
    op = event[&#8217;op&#8217;]       <em># &#8216;c&#8217;reate / &#8216;u&#8217;pdate / &#8216;d&#8217;elete</em>
    row = event.get(&#8217;after&#8217;) or event.get(&#8217;before&#8217;)

    if op in (&#8217;u&#8217;, &#8216;d&#8217;) and row:
        user_id = row[&#8217;id&#8217;]
        <em># Delete all cache keys for this user</em>
        keys_to_invalidate =
 [
            f&#8221;user:{user_id}:profile&#8221;,
            f&#8221;user:{user_id}:balance&#8221;,
            f&#8221;user:{user_id}:permissions&#8221;,
        ]
        if keys_to_invalidate:
            r.delete(*keys_to_invalidate)
            print(f&#8221;Invalidated {len(keys_to_invalidate)} keys for user {user_id}&#8221;)</code></pre><h2>Production-grade patterns</h2><h3>Pattern 1: Cache stampede prevention with probabilistic early expiry</h3><p>Instead of waiting for a key to expire before refreshing, <em>probabilistically</em> refresh it slightly before expiry based on how long the last fetch took. This eliminates the &#8220;everyone misses at exactly the same moment&#8221; problem without locking.</p><pre><code>async function xfetch&lt;T&gt;
(
  key: string,
  fetchFn: () =&gt; Promise&lt;T&gt;,
  ttl: number,
  beta = 1
): Promise&lt;T&gt;
 {
  const raw = await redis.get(key);
  if (raw) 
{
    const { value, expiry, delta } = JSON.parse(raw);
    <em>// Should we refresh early? Larger delta = more likely to refresh</em>
    const now = Date.now() / 1000;
    if (now - delta * beta * Math.log(Math.random()) &lt; expiry) 
{
      return value; <em>// still &#8220;fresh enough&#8221;</em>
    }
  }

  const start = Date.now();
  const value = await fetchFn();
  const delta = (Date.now() - start) / 1000; <em>// recompute time in seconds</em>
  const expiry = Date.now() / 1000 + ttl;

  await redis.setex(key, ttl, JSON.stringify({ value, expiry, delta }));
  return value;
}</code></pre><h3>Pattern 2: Multi-tier caching (L1 in-process + L2 Redis)</h3><p>For extreme read throughput, add an in-process L1 cache (e.g. LRU map with a 5-second TTL) in front of Redis. This can reduce Redis round-trips by 95% for the hottest keys &#8212; at the cost of brief per-instance inconsistency.</p><pre><code>import LRU from &#8216;lru-cache&#8217;;

const l1 = new LRU&lt;string, unknown&gt;
(
{
  max: 500,
  ttl: 5_000, <em>// 5-second in-process cache</em>
}
);

async function tieredGet&lt;T&gt;(key: string, fetchFn: () =&gt; Promise&lt;T&gt;): Promise&lt;T&gt; 
{
  <em>// L1: in-process (sub-millisecond)</em>
  const l1Hit = l1.get(key);
  if (l1Hit !== undefined) return l1Hit as T;

  <em>// L2: Redis (1&#8211;3ms)</em>
  const l2Raw = await redis.get(key);
  if (l2Raw) 
{
    const val = JSON.parse(l2Raw) as T;
    l1.set(key, val); <em>// populate L1</em>
    return val;
  }

  <em>// L3: Database (50&#8211;200ms)</em>
  const data = await fetchFn();
  await redis.setex(key, 300, JSON.stringify(data));
  l1.set(key, data);
  return data;
}</code></pre><h3>Pattern 3: Negative caching &#8212; cache the misses</h3><p>If your application frequently queries for records that don&#8217;t exist (user ID not found, promo code invalid), those misses hit the database every time. Cache a sentinel value with a short TTL to absorb the load.</p><h2>Strategy comparison at a glance</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fGFi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cadfc5-b942-4cf5-bca7-64a05af3c839_1111x689.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fGFi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cadfc5-b942-4cf5-bca7-64a05af3c839_1111x689.png 424w, https://substackcdn.com/image/fetch/$s_!fGFi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cadfc5-b942-4cf5-bca7-64a05af3c839_1111x689.png 848w, https://substackcdn.com/image/fetch/$s_!fGFi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cadfc5-b942-4cf5-bca7-64a05af3c839_1111x689.png 1272w, https://substackcdn.com/image/fetch/$s_!fGFi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cadfc5-b942-4cf5-bca7-64a05af3c839_1111x689.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fGFi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cadfc5-b942-4cf5-bca7-64a05af3c839_1111x689.png" width="1111" height="689" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/14cadfc5-b942-4cf5-bca7-64a05af3c839_1111x689.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:689,&quot;width&quot;:1111,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:86435,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://devopsguyankit.substack.com/i/197450839?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cadfc5-b942-4cf5-bca7-64a05af3c839_1111x689.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!fGFi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cadfc5-b942-4cf5-bca7-64a05af3c839_1111x689.png 424w, https://substackcdn.com/image/fetch/$s_!fGFi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cadfc5-b942-4cf5-bca7-64a05af3c839_1111x689.png 848w, https://substackcdn.com/image/fetch/$s_!fGFi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cadfc5-b942-4cf5-bca7-64a05af3c839_1111x689.png 1272w, https://substackcdn.com/image/fetch/$s_!fGFi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cadfc5-b942-4cf5-bca7-64a05af3c839_1111x689.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Observability, metrics you must track</h2><p>A cache you cannot observe is a liability. These are the non-negotiable metrics for any production caching layer:</p><ul><li><p><strong>1 Hit ratio</strong> &#8212; <code>hits / (hits + misses)</code>. Below 80% means your TTLs are too short, your key space is too wide, or you&#8217;re caching the wrong data. Target &gt;95% for stable workloads.</p></li><li><p><strong>2 Latency percentiles</strong> &#8212; Track P50, P95, P99 for both cache hits and misses separately. Spikes in hit latency often reveal Redis memory pressure or network issues.</p></li><li><p><strong>3 Memory usage &amp; eviction rate</strong> &#8212; High eviction rates (<code>evicted_keys</code> in Redis <code>INFO stats</code>) indicate your cache is undersized. Evictions destroy hit ratio and cause latency spikes.</p></li><li><p><strong>4 Key count &amp; TTL distribution</strong> &#8212; Unexpectedly large key counts signal key leaks (no TTL set). Use <code>redis-cli --bigkeys</code> and <code>OBJECT ENCODING</code> to audit.</p></li><li><p><strong>5 Connection pool saturation</strong> &#8212; If your Redis client&#8217;s connection pool is full, requests queue and latency explodes. Monitor <code>connected_clients</code> and pool wait time.</p></li></ul><pre><code><em># Overall stats (keyspace hits, misses, evictions)</em>
redis-cli INFO stats | grep -E &#8220;keyspace|evicted|connected&#8221;

<em># Memory breakdown</em>
redis-cli INFO memory | grep -E &#8220;used_memory_human|mem_fragmentation&#8221;

<em># Find largest keys (run during off-peak &#8212; scans all keys)</em>
redis-cli --bigkeys

<em># Real-time command throughput</em>
redis-cli --stat

<em># Keys expiring in the next 60 seconds (sampling)</em>
redis-cli DEBUG SLEEP 0 &amp;&amp; redis-cli INFO keyspace</code></pre><h2>Common pitfalls and how to avoid them</h2><p><strong>Pitfall 1: Storing mutable objects by reference.</strong> Serialise everything to JSON before storing. If you cache a live object reference, mutations in the application code will silently corrupt the cached data and you won't know until a user reports stale behaviour.</p><p><strong>Pitfall 2: No TTL on any key.</strong> Memory is finite. Every cached key must have a TTL,  even if it&#8217;s 24 hours. Keys without TTL are permanent until explicitly deleted, and they will eventually fill your Redis instance and trigger evictions of hot data.</p><p><strong>Pitfall 3: Caching non-serialisable data.</strong> Dates, Maps, Sets, class instances, and circular references don&#8217;t survive JSON serialisation. Use a schema (e.g. Zod or class-transformer) to serialise/deserialise cache values and catch these bugs in development.</p><p><strong>Pitfall 4: Large objects in cache.</strong> Redis is not a blob store. Keep individual values under 1 MB. Large values increase serialisation overhead, consume memory disproportionately, and can block Redis&#8217;s single-threaded event loop on write. Compress with gzip for anything above 10 KB.</p><p><strong>Pitfall 5: Cache as primary storage.</strong> Redis is ephemeral by default. Even with AOF/RDB persistence, treat it as a best-effort cache. Always have a database as the source of truth. If cache data must survive restarts, you need a different tool (e.g. Postgres with a fast index).</p><div class="pullquote"><p><strong>Caching is a spectrum</strong>. On one end: a simple <code>SETEX</code> wrapping your most expensive SQL query with a five-minute TTL. On the other end: a multi-tier, CDC-driven, probabilistically refreshed cache layer with per-key observability and automated eviction tuning. Most production systems live somewhere in the middle and that&#8217;s exactly right.</p></div><p>Start with cache-aside and a sensible TTL. Measure your hit ratio. Add explicit invalidation where staleness causes real pain. Only then reach for event-driven invalidation or write-back. Complexity should be earned by evidence, not assumed upfront.</p><p>The goal is not a perfect cache. The goal is a predictably fast system with clear failure modes that your on-call engineer can debug at 2 a.m. without a Ph.D. in distributed systems.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://devopsguyankit.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading DevOps IN SPACE! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>