<?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"><channel><title><![CDATA[Yash Dhawane]]></title><description><![CDATA[Backend Engineer sharing practical articles on Node.js, System Design, AI Agents, RAG, Cloud, and scalable software engineering.]]></description><link>https://yashdhawane.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 30 Oct 2026 06:27:23 GMT</lastBuildDate><atom:link href="https://yashdhawane.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Scalability: Why Adding More Servers Isn't Always the Answer]]></title><description><![CDATA[Learn how backend systems scale with vertical and horizontal scaling, caching, read replicas, database bottlenecks, and sharding.]]></description><link>https://yashdhawane.hashnode.dev/scalability-why-adding-more-servers-isn-t-always-the-answer</link><guid isPermaLink="true">https://yashdhawane.hashnode.dev/scalability-why-adding-more-servers-isn-t-always-the-answer</guid><category><![CDATA[System Design]]></category><category><![CDATA[backend]]></category><category><![CDATA[scalability]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[#learning-in-public]]></category><dc:creator><![CDATA[Yash Dhawane]]></dc:creator><pubDate>Thu, 29 Oct 2026 20:11:51 GMT</pubDate><content:encoded><![CDATA[<p>I'm learning High-Level Design (HLD), and one of my first lessons was about scalability.</p>
<p>Before this, when I thought about a system struggling with increasing traffic, my instinct was simple: add more servers, introduce Redis, or move to microservices.</p>
<p>But the more I studied scalability, the more I realized that <strong>adding infrastructure without understanding the bottleneck can make a system more expensive without actually solving the problem.</strong></p>
<p>Here's what I learned.</p>
<h2>1. Vertical vs. Horizontal Scaling</h2>
<p>Imagine a coffee shop with one barista who can serve 30 customers per hour. Now demand doubles.</p>
<p>You have two options:</p>
<ul>
<li><strong>Vertical scaling (scale up):</strong> Hire a faster barista who can serve 60 customers per hour.</li>
<li><strong>Horizontal scaling (scale out):</strong> Add another barista and distribute the orders between them.</li>
</ul>
<p>Vertical scaling is simpler because you don't need to change much. But eventually, you hit hardware limits, costs increase, and one machine remains a single point of failure.</p>
<p>Horizontal scaling distributes the workload and improves resilience, but now you have coordination problems, concurrency, and partial failures to handle.</p>
<p>The important lesson?</p>
<p><strong>Start simple. Scale horizontally when the system actually needs it, not because it sounds more advanced.</strong></p>
<p>The question I’m learning to ask first is:</p>
<p><strong>What resource is saturated, what workload is causing it, and which change will actually relieve that pressure?</strong></p>
<p>That shift changes how I think about scalability.</p>
<h2>2. Start With the Workload, Not the Architecture</h2>
<p>Scalability is often discussed as the ability to handle more traffic. In practice, traffic volume alone doesn't tell us enough.</p>
<p>Consider two applications receiving 10,000 requests per minute.</p>
<p>One serves mostly cacheable product pages. The other processes financial transactions that require consistent reads and writes to a shared database.</p>
<p>The request rate is identical, but the bottlenecks, consistency requirements, and scaling strategies can be very different.</p>
<p>Before changing the architecture, I would want to understand:</p>
<ul>
<li><strong>Traffic shape:</strong> Are requests evenly distributed, or do they arrive in bursts?</li>
<li><strong>Read/write ratio:</strong> Is the workload dominated by reads, writes, or both?</li>
<li><strong>Latency distribution:</strong> Is average latency acceptable while p95 or p99 latency is deteriorating?</li>
<li><strong>Resource saturation:</strong> Is the constraint CPU, memory, database connections, disk I/O, or network capacity?</li>
<li><strong>Correctness requirements:</strong> Can a user tolerate stale data, or must a write be immediately visible to subsequent reads?</li>
</ul>
<p>These questions establish the constraints the architecture needs to satisfy.</p>
<p>Without them, scaling decisions are mostly guesses.</p>
<h2>3. Statelessness Makes Horizontal Scaling Easier</h2>
<p>Suppose I build an API using Node.js and store user sessions in the application's memory.</p>
<p>Initially, everything works fine.</p>
<p>Then I run four instances behind a load balancer. A user's first request reaches Server A, but the next request reaches Server C.</p>
<p>Server C doesn't know about the session stored in Server A's memory.</p>
<p>This is where stateless design matters.</p>
<p>Instead of keeping session state inside individual application processes, I can move it to a shared store such as Redis or use an appropriate signed token.</p>
<p>Now, any application instance can handle a request.</p>
<p>This also explains an important distinction in Kubernetes:</p>
<ul>
<li>A Deployment manages interchangeable application pods.</li>
<li>A StatefulSet provides stable identities and persistent storage for workloads that need them.</li>
</ul>
<p>The broader principle is simple: keep the application layer disposable and isolate state where it can be managed deliberately.</p>
<h2>4. Read Scaling and Write Scaling Are Different Problems</h2>
<p>This was one of the most important distinctions I learned.</p>
<p>Imagine a system where 70% of requests are reads and 30% are writes.</p>
<p>Adding read replicas can distribute read traffic across multiple database instances. A cache can reduce repeated database queries, and a CDN can serve static content closer to users.</p>
<p>But there's a catch.</p>
<p><strong>Read replicas don't solve a write bottleneck.</strong></p>
<p>Writes still go through the authoritative primary database. The primary must process those writes and stream changes to its replicas through the write-ahead log (WAL).</p>
<p>Adding more replicas increases read capacity, but it doesn't automatically increase write capacity. It can even add overhead to the primary.</p>
<p>Write scaling requires different approaches, such as sharding or batching, each with its own trade-offs.</p>
<p>Another detail worth remembering: replicas can lag behind the primary. A user who updates their profile and immediately reads it from a replica might temporarily see stale data.</p>
<p>One solution is to route that user's recent reads to the primary for a short period.</p>
<p>This is why choosing a scaling strategy requires understanding the workload, not just the technology.</p>
<h2>5. Profile First, Scale Second</h2>
<p>Consider a database running at 80% CPU during peak traffic.</p>
<p>Should I immediately add replicas or upgrade the instance?</p>
<p>Not necessarily.</p>
<p>The first step is to understand where the database is spending its resources.</p>
<p>For PostgreSQL, tools such as <code>pg_stat_statements</code> and <code>EXPLAIN ANALYZE</code> help identify expensive queries and execution plans.</p>
<p>A missing index, an inefficient query, or an N+1 query pattern might be the actual problem.</p>
<p>Adding more infrastructure before fixing these issues could mean paying more to execute the same inefficient workload.</p>
<p>The order matters:</p>
<ol>
<li>Profile the bottleneck.</li>
<li>Fix inefficient queries and missing indexes.</li>
<li>Scale the primary vertically when appropriate.</li>
<li>Add read replicas for read-heavy workloads.</li>
<li>Cache frequently accessed data and use a CDN where appropriate.</li>
<li>Consider partitioning and, eventually, sharding when simpler approaches aren't enough.</li>
</ol>
<p>The exact order of infrastructure changes depends on the workload. The important rule is to diagnose first and validate each change.</p>
<h2>6. Caching Helps, but Invalidation Is Hard</h2>
<p>Redis is useful when the same data is requested repeatedly.</p>
<p>Instead of sending every request to PostgreSQL, I can cache frequently accessed resources and reduce database load.</p>
<p>But caching introduces another problem: keeping cached data consistent with the source of truth.</p>
<p>Using a TTL (time to live) limits how long an entry remains cached, but it doesn't guarantee that the data stays fresh until expiration.</p>
<p>A better approach for many use cases is to invalidate or update relevant cache entries when the underlying data changes, while using TTL as a safety net.</p>
<p>If you're new to caching, start here:</p>
<p><a href="https://yashdhawane.hashnode.dev/caching-strategies-and-cache-invalidation-a-practical-guide-for-backend-developers"><strong>Caching Strategies and Cache Invalidation: A Practical Guide for Backend Developers</strong></a></p>
<h2>7. Real Systems Don't Scale Overnight</h2>
<p>Two examples from the chapter reinforced this for me.</p>
<p><strong>Figma:</strong> Its database stack grew dramatically through approaches including vertical scaling, caching, and vertical partitioning before its first horizontally sharded table. That first sharding effort took nine months.</p>
<p><strong>Notion:</strong> It continued with a substantial PostgreSQL setup before sharding became necessary for its specific database challenges.</p>
<p>These examples challenged my assumption that successful systems must adopt distributed architectures early.</p>
<p>Sometimes the best engineering decision is to keep the existing architecture, improve it, and postpone complexity until the evidence justifies it.</p>
<h2>My Biggest Takeaway</h2>
<p><strong>The goal is to build a system that meets its performance, availability, and cost requirements with the least unnecessary complexity.</strong></p>
]]></content:encoded></item><item><title><![CDATA[Caching Strategies and Cache Invalidation: A Practical Guide for Backend Developers]]></title><description><![CDATA[Caching makes applications faster. But keeping cached data correct when the original data changes is where things get interesting. Imagine you're building…]]></description><link>https://yashdhawane.hashnode.dev/caching-strategies-and-cache-invalidation-a-practical-guide-for-backend-developers</link><guid isPermaLink="true">https://yashdhawane.hashnode.dev/caching-strategies-and-cache-invalidation-a-practical-guide-for-backend-developers</guid><category><![CDATA[backend]]></category><category><![CDATA[System Design]]></category><category><![CDATA[Redis]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Databases]]></category><dc:creator><![CDATA[Yash Dhawane]]></dc:creator><pubDate>Sat, 24 Oct 2026 13:54:31 GMT</pubDate><content:encoded><![CDATA[<p><strong>Caching makes applications faster. But keeping cached data correct when the original data changes is where things get interesting.</strong></p>
<p>Imagine you're building an e-commerce application. Every time a user opens a product page, your backend fetches the product details from a database.</p>
<p>Now imagine thousands of users requesting the same product every second.</p>
<p>Do we really need to query the database for the same information every single time?</p>
<p>Probably not.</p>
<p>This is where caching comes in.</p>
<p>But caching introduces another problem: what happens when the original data changes, but the cached copy doesn't?</p>
<p>In this article, we'll explore caching strategies, cache invalidation, TTL, cache stampedes, and eviction policies using practical examples that backend developers can understand.</p>
<h2>1. What Is Caching?</h2>
<p>Caching is the process of temporarily storing frequently accessed data in a faster storage layer so that future requests can retrieve it without repeating expensive operations.</p>
<p>For example, consider an e-commerce application built with Node.js, MongoDB, and Redis.</p>
<p>When a user requests a product, the backend queries MongoDB and returns the result.</p>
<p>If another user requests the same product, the backend performs the same query again.</p>
<p>As traffic increases, these repeated database queries consume resources and can increase response times.</p>
<p>Instead, we can store the product in Redis after the first database query.</p>
<p>On subsequent requests, the backend retrieves the product from Redis rather than querying MongoDB again.</p>
<p>The basic flow becomes:</p>
<ol>
<li>A user requests a product.</li>
<li>The backend checks Redis.</li>
<li>If the product exists in Redis, return it.</li>
<li>Otherwise, fetch it from MongoDB.</li>
<li>Store the result in Redis for future requests.</li>
<li>Return the product to the user.</li>
</ol>
<p>The database remains the <strong>source of truth</strong>, while the cache stores a temporary copy for faster access.</p>
<h3>Cache hit vs. cache miss</h3>
<p>A <strong>cache hit</strong> occurs when the requested data is available in the cache.</p>
<p>A <strong>cache miss</strong> occurs when the data is missing or has expired, so the application must fetch it from another source.</p>
<p>The goal is to maximize useful cache hits without serving unacceptable amounts of stale data.</p>
<h2>2. The Real Problem: Cache Invalidation</h2>
<p>Let's say a product costs ₹1,000.</p>
<p>Initially:</p>
<ul>
<li>MongoDB: ₹1,000</li>
<li>Redis: ₹1,000</li>
</ul>
<p>Now the seller changes the price to ₹800.</p>
<p>The database contains ₹800, but Redis still contains ₹1,000.</p>
<p>If the application reads from Redis, customers may see the old price.</p>
<p>This is called <strong>stale data</strong>.</p>
<p>Cache invalidation is the process of updating or removing cached data when the underlying data changes.</p>
<p>It sounds simple, but consider what happens when the same product appears in multiple cached views, several application servers are running, and reads and writes happen simultaneously.</p>
<p>A single database update can affect multiple cached representations of the same information.</p>
<p>This is why cache invalidation is a distributed systems problem, not merely a performance optimization.</p>
<p>The main question behind every caching strategy is:</p>
<p><strong>When the original data changes, how does the cache find out, and what does the reader see in the meantime?</strong></p>
<p>Let's explore the different approaches.</p>
<h2>3. Cache-Aside: The Most Common Caching Pattern</h2>
<p>Cache-aside, also called lazy loading, is a common general-purpose caching strategy.</p>
<p>The application manages the cache explicitly.</p>
<p>It checks the cache first and queries the database only when the requested data isn't available.</p>
<h3>How it works</h3>
<p>Suppose a user requests <code>GET /products/101</code>.</p>
<p>The backend follows this flow:</p>
<ol>
<li>Check Redis for <code>product:101</code>.</li>
<li>If the entry exists, return it.</li>
<li>If the entry doesn't exist, query MongoDB.</li>
<li>Store the result in Redis.</li>
<li>Return the result to the user.</li>
</ol>
<p>The next request can retrieve the product directly from Redis.</p>
<h3>Example</h3>
<pre><code class="language-javascript">async function getProduct(productId) {
  const cacheKey = `product:${productId}`;

  // Check the cache first
  const cachedProduct = await redis.get(cacheKey);

  if (cachedProduct) {
    return JSON.parse(cachedProduct);
  }

  // Cache miss: fetch from the database
  const product = await db.products.findById(productId);

  if (!product) {
    return null;
  }

  // Cache the result for future requests
  await redis.set(
    cacheKey,
    JSON.stringify(product),
    { EX: 300 }
  );

  return product;
}
</code></pre>
<p>This example uses a five-minute TTL.</p>
<p>The exact Redis command syntax can vary by client version, but the pattern remains the same.</p>
<h3>Advantages</h3>
<ul>
<li>The database is queried only when necessary.</li>
<li>Frequently requested data can be served quickly.</li>
<li>Data is loaded into the cache only when requested.</li>
<li>The application controls its caching behavior.</li>
</ul>
<h3>Disadvantages</h3>
<ul>
<li>The first request after a cache miss is slower.</li>
<li>Cached data can become stale when the database changes.</li>
<li>Concurrent cache misses can trigger duplicate database queries.</li>
</ul>
<p>Cache-aside is a good starting point for product details, user profiles, blog posts, and other frequently read data.</p>
<p>However, it doesn't automatically solve cache invalidation. We need another strategy to keep the cached data fresh.</p>
<h2>4. Write-Through vs. Write-Back</h2>
<p>Cache-aside primarily describes how data is loaded during reads.</p>
<p>Write-through and write-back describe what happens when data is written.</p>
<h3>Write-through caching</h3>
<p>With write-through, a write updates the cache and the database synchronously as part of the same write operation.</p>
<p>Suppose a user changes their profile name from Rahul to Rohan.</p>
<p>The application updates the database and the cached profile before considering the write complete.</p>
<p>The intended result is:</p>
<ul>
<li>Database: Rohan</li>
<li>Cache: Rohan</li>
</ul>
<p>Readers using the same cache path can then see the updated value.</p>
<p><strong>Advantages:</strong></p>
<ul>
<li>Cached data is updated as part of the write path.</li>
<li>The risk of serving an outdated value is reduced.</li>
<li>It can be useful when fresh cached reads matter.</li>
</ul>
<p><strong>Disadvantages:</strong></p>
<ul>
<li>Writes usually take longer because both systems must be updated.</li>
<li>If one update succeeds and the other fails, the two copies can disagree.</li>
<li>Failure handling and recovery are still necessary.</li>
</ul>
<p>Write-through doesn't automatically make two independent systems transactionally consistent. The implementation must handle partial failures.</p>
<h3>Write-back caching</h3>
<p>Write-back, also called write-behind, takes a different approach.</p>
<p>The application updates the cache first and writes the change to the database asynchronously later.</p>
<p>For example:</p>
<ol>
<li>The user changes a value.</li>
<li>The cache is updated.</li>
<li>The application acknowledges the write.</li>
<li>A background process persists the change to the database.</li>
</ol>
<p>The write can be acknowledged quickly because the database update doesn't need to finish first.</p>
<p><strong>Advantages:</strong></p>
<ul>
<li>Lower write latency.</li>
<li>Multiple changes can potentially be batched.</li>
<li>Useful for write-heavy workloads that tolerate delayed persistence.</li>
</ul>
<p><strong>Disadvantages:</strong></p>
<ul>
<li>Pending changes may be lost if the cache fails before they reach durable storage.</li>
<li>The database may temporarily contain an older value.</li>
<li>Retries, ordering, durability, and recovery require careful design.</li>
</ul>
<p>Write-back isn't automatically better than write-through. It trades some persistence guarantees for faster writes.</p>
<p>For many ordinary backend applications, cache-aside is the simpler starting point. Write-back should be introduced only when the workload justifies its additional complexity.</p>
<h2>5. TTL: Time to Live</h2>
<p>TTL stands for <strong>Time To Live</strong>.</p>
<p>It defines how long a cached entry can remain valid before it expires.</p>
<p>Think of it as an expiry date for cached data.</p>
<p>Suppose a product is cached at 12:00 PM with a TTL of five minutes.</p>
<ul>
<li>12:00 PM: Redis stores the product at ₹1,000.</li>
<li>12:02 PM: The database price changes to ₹800.</li>
<li>12:03 PM: A request may still receive the cached price of ₹1,000.</li>
<li>Around 12:05 PM: The entry expires.</li>
<li>The next cache miss fetches the current database value and can cache it again.</li>
</ul>
<p>The cache can therefore serve stale data during the remaining TTL window.</p>
<h3>Why not use a very long TTL?</h3>
<p>A long TTL reduces cache misses and database load, but users may see outdated information for longer.</p>
<p>A short TTL allows cached data to expire sooner, but increases the number of database requests.</p>
<p>This is the fundamental trade-off.</p>
<table>
<thead>
<tr>
<th>Data type</th>
<th>Possible caching approach</th>
</tr>
</thead>
<tbody><tr>
<td>Product descriptions</td>
<td>Longer TTL may be acceptable</td>
</tr>
<tr>
<td>Public blog posts</td>
<td>TTL plus invalidation on edits</td>
</tr>
<tr>
<td>User profiles</td>
<td>TTL plus invalidation on updates</td>
</tr>
<tr>
<td>Frequently changing inventory</td>
<td>Short TTL or event-driven invalidation</td>
</tr>
<tr>
<td>Real-time financial balances</td>
<td>Don't rely on TTL alone for correctness</td>
</tr>
</tbody></table>
<p>These are illustrative choices, not universal rules.</p>
<p>The right TTL depends on how often the data changes, how expensive it is to fetch, and how harmful stale data would be.</p>
<p>TTL is one of the simplest ways to limit staleness, but it doesn't guarantee that the cache always matches the database.</p>
<h2>6. Explicit Cache Invalidation</h2>
<p>TTL waits for cached data to expire.</p>
<p>Explicit invalidation removes or updates the cached entry when the underlying data changes.</p>
<p>Suppose a user updates their profile.</p>
<p>The application updates the database and deletes the corresponding cached profile.</p>
<p>The next read encounters a cache miss and fetches the updated record.</p>
<p>A simplified Node.js example:</p>
<pre><code class="language-javascript">async function updateProfile(userId, changes) {
  await db.users.update(userId, changes);

  await redis.del(`user:${userId}`);

  return { success: true };
}
</code></pre>
<p>The database update happens first, followed by cache deletion.</p>
<p>This is often a useful pattern because it avoids deliberately putting the new database value into the cache before the database update succeeds.</p>
<p>However, there's an important failure case.</p>
<p>What if the database update succeeds but Redis deletion fails?</p>
<p>The database contains the new profile, while the cache may still contain the old one.</p>
<p>A production implementation needs a plan for retries, monitoring, and recovery.</p>
<h3>Why invalidation becomes difficult at scale</h3>
<p>Consider a user whose information appears in several cached objects:</p>
<ul>
<li><code>user:42</code> — the user's profile</li>
<li><code>feed:42</code> — a personalized feed</li>
<li><code>team:7:members</code> — a team membership list</li>
<li>Search results containing that user</li>
</ul>
<p>Deleting only <code>user:42</code> might leave outdated information in the other cached objects.</p>
<p>The challenge isn't just deleting one cache key. It's identifying every cached representation affected by the database change.</p>
<p>This is why cache invalidation becomes more complicated as applications grow.</p>
<h3>TTL vs. explicit invalidation</h3>
<p>TTL is simple and provides an automatic expiry mechanism.</p>
<p>Explicit invalidation can make changes visible sooner, but it requires correct logic on the write path.</p>
<p>Many production systems combine both approaches:</p>
<ul>
<li>Invalidate relevant keys when data changes.</li>
<li>Set a TTL as a fallback.</li>
<li>Add retries or durable event processing when missed invalidations would be costly.</li>
</ul>
<h2>7. Cache Stampede: When a Cache Overloads the Database</h2>
<p>A cache stampede, also called a thundering herd, occurs when many requests miss the same cache entry simultaneously.</p>
<p>Imagine a popular article receives thousands of requests every second.</p>
<p>Its cached entry expires.</p>
<p>Without protection, many requests may query the database at the same time to fetch exactly the same article.</p>
<p>Instead of one database query, the application may issue hundreds or thousands.</p>
<p>This creates a burst of unnecessary work and can overload the database.</p>
<h3>Solution 1: Locking or single-flight requests</h3>
<p>Allow one request to fetch the missing data while other requests wait for the result or use an appropriate fallback.</p>
<p>The flow becomes:</p>
<ol>
<li>Multiple requests encounter a cache miss.</li>
<li>One request becomes responsible for refreshing the entry.</li>
<li>Other requests wait or use a safe alternative.</li>
<li>The responsible request fetches the data from the database.</li>
<li>The fresh result is cached and reused.</li>
</ol>
<p>In a distributed deployment, the coordination mechanism must work across application instances. A local in-memory lock won't coordinate independent server processes.</p>
<h3>Solution 2: Add randomness to TTL</h3>
<p>If many entries expire at exactly the same time, expiration jitter can spread their expiry times.</p>
<pre><code class="language-javascript">const ttlSeconds =
  300 + Math.floor(Math.random() * 60);
</code></pre>
<p>Each entry gets a TTL between 300 and 359 seconds.</p>
<p>This helps reduce synchronized expiration across different entries.</p>
<p>However, TTL jitter alone doesn't eliminate a stampede on one extremely popular key.</p>
<h3>Other approaches</h3>
<ul>
<li>Serve stale data while refreshing it in the background, when stale data is acceptable.</li>
<li>Refresh popular entries before they expire.</li>
<li>Limit concurrent database requests.</li>
<li>Use request coalescing so concurrent callers share the same refresh operation.</li>
</ul>
<p>The key lesson is that expiration and stampede prevention are separate concerns.</p>
<h2>8. Eviction Policies: What Happens When the Cache Is Full?</h2>
<p>Cache memory is limited.</p>
<p>Even if entries haven't expired, the cache may need to remove some of them to make room for new data.</p>
<p>An eviction policy decides which entries should be removed when the cache reaches its memory limit.</p>
<p>One common strategy is <strong>LRU — Least Recently Used</strong>.</p>
<p>LRU removes the entry that hasn't been accessed for the longest time.</p>
<p>Imagine a cache has room for three products:</p>
<table>
<thead>
<tr>
<th>Product</th>
<th>Last accessed</th>
</tr>
</thead>
<tbody><tr>
<td>A</td>
<td>Just now</td>
</tr>
<tr>
<td>B</td>
<td>10 seconds ago</td>
</tr>
<tr>
<td>C</td>
<td>5 minutes ago</td>
</tr>
</tbody></table>
<p>When a new product needs to be stored, an LRU cache would generally choose C for eviction.</p>
<p>The reasoning is that recently accessed data is more likely to be requested again soon.</p>
<p>LRU is useful, but it isn't always the best choice for every workload. Some systems use alternative policies based on access frequency or other factors.</p>
<p>Also, expiration and eviction are different:</p>
<ul>
<li>Expiration removes data because its validity period has ended.</li>
<li>Eviction removes data to manage limited cache capacity.</li>
</ul>
<p>A cache can have entries evicted before their TTL expires.</p>
<h2>9. Where Can Caching Happen?</h2>
<p>Caching can exist at several layers of an application.</p>
<h3>Browser cache</h3>
<p>Browsers can cache images, CSS, JavaScript files, and eligible HTTP responses.</p>
<p>This reduces repeated downloads and can make websites feel faster.</p>
<h3>CDN cache</h3>
<p>Content delivery networks such as Cloudflare can cache eligible content at edge locations closer to users.</p>
<p>This reduces requests reaching the origin server and improves delivery speed across regions.</p>
<h3>Distributed application cache</h3>
<p>Redis and Memcached are examples of caches that multiple backend instances can share.</p>
<p>They are useful for frequently accessed database records, sessions, computed results, and other reusable data.</p>
<h3>In-process cache</h3>
<p>An application can also store data directly in its own memory, using a <code>Map</code> or a caching library.</p>
<p>This can be very fast, but every process has its own copy. The cached data also disappears when the process restarts unless it is reconstructed.</p>
<h3>How these layers work together</h3>
<p>A request for a product page might use browser caching for static assets, CDN caching for public content, Redis for frequently accessed application data, and MongoDB as the source of truth.</p>
<p>Not every request passes through every layer.</p>
<p>Each layer needs an appropriate freshness policy because invalidating one layer doesn't necessarily invalidate the others.</p>
<h2>10. What Should You Cache?</h2>
<p>Caching is not automatically beneficial for every kind of data.</p>
<p>It works best when data is expensive to fetch or compute, requested repeatedly, and changes infrequently enough that cached copies remain useful.</p>
<p>Consider these examples:</p>
<table>
<thead>
<tr>
<th>Data</th>
<th>Caching suitability</th>
</tr>
</thead>
<tbody><tr>
<td>Product descriptions</td>
<td>High</td>
</tr>
<tr>
<td>Popular blog posts</td>
<td>High</td>
</tr>
<tr>
<td>User profiles</td>
<td>Often high</td>
</tr>
<tr>
<td>Public application configuration</td>
<td>Often high</td>
</tr>
<tr>
<td>Real-time bank balances</td>
<td>Requires special care</td>
</tr>
<tr>
<td>Frequently changing stock levels</td>
<td>Depends on correctness requirements</td>
</tr>
<tr>
<td>Data requested only once</td>
<td>Often low</td>
</tr>
</tbody></table>
<p>Before introducing a cache, ask five questions:</p>
<ol>
<li>Is fetching or computing this data expensive?</li>
<li>Is it requested repeatedly?</li>
<li>How frequently does it change?</li>
<li>How much staleness is acceptable?</li>
<li>Can we reliably refresh or invalidate the cached copy?</li>
</ol>
<p>If caching provides little improvement while introducing significant consistency risks, it may not be worth the complexity.</p>
<h2>11. Common Caching Mistakes</h2>
<p>Here are some mistakes worth avoiding when designing backend systems.</p>
<p><strong>1. Adding a cache without an invalidation strategy</strong></p>
<p>Cached data may remain outdated longer than expected, particularly when entries don't expire.</p>
<p><strong>2. Choosing a TTL without understanding the data</strong></p>
<p>A five-minute TTL might be perfectly acceptable for a blog post but inappropriate for a value that affects a financial transaction.</p>
<p><strong>3. Ignoring cache stampedes</strong></p>
<p>A popular entry expiring can cause a sudden surge of database queries.</p>
<p><strong>4. Caching data that changes constantly</strong></p>
<p>The cache may miss frequently, providing little benefit while adding complexity.</p>
<p><strong>5. Invalidating only one representation of the data</strong></p>
<p>Other cache keys, application instances, or HTTP caching layers may still contain outdated values.</p>
<p><strong>6. Assuming the cache and database update atomically</strong></p>
<p>They are usually separate systems. Partial failures can leave them inconsistent, so production systems need appropriate recovery mechanisms.</p>
<p>The important point is that caching introduces operational responsibilities alongside performance benefits.</p>
<h2>12. How to Choose the Right Strategy</h2>
<p>You don't need to implement every caching strategy in every application.</p>
<p>Start with the actual requirements.</p>
<table>
<thead>
<tr>
<th>Requirement</th>
<th>Possible starting point</th>
</tr>
</thead>
<tbody><tr>
<td>General-purpose read caching</td>
<td>Cache-aside</td>
</tr>
<tr>
<td>Automatic expiration</td>
<td>TTL</td>
</tr>
<tr>
<td>Fast propagation of changes</td>
<td>Explicit invalidation</td>
</tr>
<tr>
<td>Synchronous cache updates on writes</td>
<td>Write-through</td>
</tr>
<tr>
<td>Fast writes with delayed persistence</td>
<td>Write-back, if durability is handled</td>
</tr>
<tr>
<td>Protection against simultaneous cache misses</td>
<td>Single-flight or locking</td>
</tr>
<tr>
<td>Limited cache memory</td>
<td>Appropriate eviction policy</td>
</tr>
</tbody></table>
<p>These techniques are not mutually exclusive.</p>
<p>For example, a production application can use cache-aside for reads, TTL for automatic expiration, explicit invalidation after writes, and single-flight refreshes for popular entries.</p>
<p>That combination addresses several different problems without requiring one strategy to do everything.</p>
<h2>Conclusion</h2>
<p>Caching is much more than storing data in Redis.</p>
<p>It is about deciding when data should be loaded, how long it should remain usable, what happens when the original changes, and how the system behaves when many requests arrive simultaneously.</p>
<p>The most important concepts to remember are:</p>
<ul>
<li><strong>Cache-aside</strong> explains how the application loads data into the cache.</li>
<li><strong>Write-through and write-back</strong> explain different approaches to handling writes.</li>
<li><strong>TTL</strong> automatically expires cached entries.</li>
<li><strong>Explicit invalidation</strong> removes or updates cached data after changes.</li>
<li><strong>Cache stampede protection</strong> prevents many simultaneous misses from overwhelming the database.</li>
<li><strong>Eviction policies</strong> determine what gets removed when memory is limited.</li>
</ul>
<p>The central principle is simple:</p>
<p><strong>A cache is a performance optimization, not the source of truth.</strong></p>
<p>Good caching improves speed and reduces database load without compromising the level of correctness the application requires.</p>
<p>The best strategy isn't necessarily the fastest one. It's the strategy that balances performance, consistency, failure handling, and complexity for your particular workload.</p>
<hr />
<h3>Further Reading</h3>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching">Caching in HTTP — MDN Web Docs</a></li>
<li><a href="https://redis.io/docs/latest/">Redis Documentation</a></li>
<li><a href="https://en.wikipedia.org/wiki/Cache_replacement_policies">Cache replacement policies — Wikipedia</a></li>
<li><a href="https://en.wikipedia.org/wiki/Cache_stampede">Cache stampede — Wikipedia</a></li>
</ul>
<p><em>If you're learning backend engineering, try implementing cache-aside with Redis in a small Node.js application. Then add TTL, invalidate entries after updates, and test what happens when multiple requests arrive after an entry expires. Those experiments will teach you more than memorizing definitions.</em></p>
]]></content:encoded></item><item><title><![CDATA[CAP Theorem Explained: AP vs CP, Quorums, Consensus, and Real-World Trade-offs]]></title><description><![CDATA[When you build a backend application, storing data in a database feels straightforward. You write a value, read it back, and expect everything to work. But…]]></description><link>https://yashdhawane.hashnode.dev/cap-theorem-explained-ap-vs-cp-quorums-consensus-and-real-world-trade-offs</link><guid isPermaLink="true">https://yashdhawane.hashnode.dev/cap-theorem-explained-ap-vs-cp-quorums-consensus-and-real-world-trade-offs</guid><dc:creator><![CDATA[Yash Dhawane]]></dc:creator><pubDate>Sat, 24 Oct 2026 10:38:34 GMT</pubDate><content:encoded><![CDATA[<p>When you build a backend application, storing data in a database feels straightforward. You write a value, read it back, and expect everything to work.</p>
<p>But what happens when your application runs across multiple servers, your database replicates data across regions, and the network suddenly breaks between two parts of your system?</p>
<p>One server might have the latest data while another still has an older copy. A server may be alive but unable to communicate with its peers. Some requests may succeed while others must wait or fail.</p>
<p>These problems lead to one of the most important concepts in distributed systems: the <strong>CAP theorem</strong>.</p>
<p>Understanding CAP is not just about memorizing Consistency, Availability, and Partition Tolerance. It is about deciding which operations your system can safely perform when parts of the infrastructure fail.</p>
<p>In this article, we will build the concepts from the ground up, explore AP and CP architectures, understand quorum and consensus, and connect everything to production backend systems.</p>
<h2>1. What Is a Distributed System?</h2>
<p>A distributed system consists of multiple computers, called nodes, that communicate over a network to provide a service.</p>
<p>For example, an e-commerce backend might run across multiple application servers and database nodes. Instead of relying on one machine, the system distributes its workload and data.</p>
<p>Distributed systems are useful because they can improve:</p>
<ul>
<li>Scalability by distributing workload.</li>
<li>Availability by avoiding dependence on a single machine.</li>
<li>Performance by serving requests closer to users.</li>
<li>Fault tolerance by surviving certain machine failures.</li>
</ul>
<p>However, distribution introduces a fundamental problem: <strong>different nodes may not always have the same information at the same time.</strong></p>
<h3>Replication</h3>
<p>Replication means maintaining copies of data on multiple nodes.</p>
<p>Imagine a database with three replicas:</p>
<ul>
<li>Node A stores product inventory.</li>
<li>Node B maintains another copy.</li>
<li>Node C maintains a third copy.</li>
</ul>
<p>Suppose the inventory is initially 10 units. A customer purchases one unit, and Node A updates the inventory to 9.</p>
<p>The update may not reach Nodes B and C immediately. During this period, Node A contains 9 while the other nodes may still contain 10.</p>
<p>This delay is called <em>replication lag</em>.</p>
<p>Replication improves resilience and read capacity, but it creates questions about which copy a client should trust.</p>
<h3>Network partitions</h3>
<p>A network partition occurs when a group of nodes cannot reliably communicate with another group.</p>
<p>Consider three database nodes:</p>
<ul>
<li>Node A can communicate with Node B.</li>
<li>Node C cannot communicate with either A or B.</li>
</ul>
<p>Node C may still be running. Its CPU, memory, and database process may be healthy. The problem is that communication has failed.</p>
<p>The system must now decide what to do with requests arriving at Node C.</p>
<p>Should it continue responding using potentially stale data? Or should it reject certain operations until it can safely coordinate with the other nodes?</p>
<p>That decision leads directly to CAP.</p>
<h2>2. Understanding the CAP Theorem</h2>
<p>CAP stands for:</p>
<ul>
<li><strong>C — Consistency</strong></li>
<li><strong>A — Availability</strong></li>
<li><strong>P — Partition Tolerance</strong></li>
</ul>
<p>The theorem states that when a network partition occurs, a distributed system cannot guarantee both linearizable consistency and full availability for every request across the affected partitions.</p>
<p>Let's understand each property.</p>
<h3>Consistency</h3>
<p>In the CAP context, consistency usually means linearizability.</p>
<p>If a write successfully changes a value from 10 to 9, a subsequent read must reflect that write, provided the read occurs after the write has completed and the system promises linearizable behavior.</p>
<p>Clients should not see an older value simply because they contacted a different replica.</p>
<h3>Availability</h3>
<p>CAP availability means that every request received by a non-failing node gets a response.</p>
<p>The response might contain stale data, or an operation might return an application-level rejection depending on how the system is defined. The important distinction is that a system cannot satisfy full CAP availability by indefinitely refusing requests that require coordination.</p>
<p>Availability does not mean the service must return the answer a client wants. It means it must respond under the formal guarantee.</p>
<h3>Partition tolerance</h3>
<p>Partition tolerance means the system is designed to operate despite communication failures between groups of nodes.</p>
<p>In real distributed systems, networks can fail, messages can be delayed, and links can become unreliable. You cannot simply assume that every node will always communicate successfully.</p>
<p>That makes partition handling a practical requirement.</p>
<h3>The CAP trade-off</h3>
<p>Suppose Node A receives a write that changes a bank balance from ₹5,000 to ₹4,000. At the same time, Node C is isolated by a network partition and receives a request to read the balance.</p>
<p>Node C has two broad choices:</p>
<ol>
<li>Return the old balance of ₹5,000, remaining responsive but potentially violating linearizable consistency.</li>
<li>Refuse or delay the read until it can establish that the answer is safe, sacrificing availability for that request.</li>
</ol>
<p>This is the core CAP trade-off.</p>
<p><strong>Important:</strong> CAP does not say a system must permanently choose only two properties. When the network is healthy, a system may provide both strong consistency and availability. The unavoidable trade-off concerns behavior during a partition.</p>
<h2>3. AP vs CP Architectures</h2>
<p>AP and CP describe different priorities during network partitions.</p>
<h3>AP: Availability and Partition Tolerance</h3>
<p>An AP-oriented system continues serving requests in reachable parts of the system, even when replicas cannot communicate.</p>
<p>It may accept updates independently, meaning different replicas temporarily hold different values.</p>
<p>Consider a social media application where users like a post.</p>
<p>Region A records 100 likes. Region B records 102 likes. A network partition prevents immediate synchronization.</p>
<p>An AP-oriented design may allow both regions to continue accepting likes. Once communication recovers, the system must reconcile the changes.</p>
<p>This approach is useful when responsiveness is more important than every client immediately seeing the latest globally consistent value.</p>
<p>Typical candidates include:</p>
<ul>
<li>Product catalog caches.</li>
<li>Social reactions and counters.</li>
<li>Recommendation data.</li>
<li>Some user preferences.</li>
<li>Certain geographically distributed content systems.</li>
</ul>
<p>These are examples of workloads that may tolerate eventual consistency, not a guarantee that every product using them is AP.</p>
<h3>CP: Consistency and Partition Tolerance</h3>
<p>A CP-oriented system prioritizes consistency during a partition, potentially rejecting or delaying operations that cannot be safely coordinated.</p>
<p>Imagine a ticket-booking system with only one seat remaining.</p>
<p>Two regions independently believe the seat is available. If both accept a booking without coordination, they may sell the same seat twice.</p>
<p>A CP-oriented design requires the relevant operations to follow a coordination protocol that prevents conflicting bookings from both being committed.</p>
<p>If one region loses access to the necessary quorum, it may stop accepting those writes.</p>
<p>This is useful for operations such as:</p>
<ul>
<li>Financial ledger updates.</li>
<li>Unique resource reservations.</li>
<li>Critical inventory allocation.</li>
<li>Distributed configuration changes.</li>
<li>Leader election and coordination.</li>
</ul>
<p>The key point is not that every operation in these applications must be CP. The consistency requirement depends on the operation and the business invariant.</p>
<h2>4. How AP Systems Work</h2>
<p>An AP-oriented system needs more than permission to accept requests during failures. It also needs mechanisms for propagating and reconciling data.</p>
<h3>Gossip protocols</h3>
<p>Gossip allows nodes to exchange information with selected peers.</p>
<p>Instead of requiring every node to contact every other node directly, each node periodically shares information with a few peers. Over time, membership and state information can spread through the cluster.</p>
<p>Gossip is useful for membership and state dissemination, but gossip alone does not guarantee that every application write is durable or conflict-free.</p>
<h3>Hinted handoff</h3>
<p>Suppose Node A receives a write intended for Node B, but B is temporarily unavailable.</p>
<p>In systems that support hinted handoff, A or another eligible node may temporarily store the update and forward it to B when B becomes available.</p>
<p>This reduces the chance that a temporary outage causes a replica to miss an update.</p>
<p>However, hinted handoff is not a universal guarantee against permanent data loss. Its effectiveness depends on the implementation, durability, retention period, and failure scenario.</p>
<h3>Anti-entropy repair</h3>
<p>Anti-entropy is a background process for finding and repairing differences between replicas.</p>
<p>For example:</p>
<ol>
<li>Node A contains version 15 of a record.</li>
<li>Node B contains version 13.</li>
<li>The repair process detects the difference.</li>
<li>The system transfers or reconciles the missing updates.</li>
</ol>
<p>Merkle trees can make comparisons more efficient by allowing systems to identify divergent ranges without comparing every record individually.</p>
<h3>Read repair</h3>
<p>Read repair occurs when a read discovers that replicas disagree and triggers reconciliation.</p>
<p>For example, a client reads the same logical record from replicas that contain versions 13 and 15. The system may return the appropriate version and repair the stale replica, depending on its consistency and repair configuration.</p>
<h3>Conflict resolution</h3>
<p>What happens when two replicas accept different updates to the same record?</p>
<p>Possible strategies include:</p>
<p><strong>Last-write-wins:</strong> Choose an update according to a timestamp or ordering rule. This is simple but can discard legitimate updates, and timestamp-based approaches can be affected by clock skew.</p>
<p><strong>Version vectors or vector clocks:</strong> Track aspects of causal history so the system can distinguish some causally related updates from concurrent updates.</p>
<p><strong>CRDTs:</strong> Use data structures designed so compatible concurrent updates can be merged predictably. Examples include certain counters and sets.</p>
<p><strong>Application-level merging:</strong> Apply business-specific rules to combine or resolve updates.</p>
<p>No strategy is universally correct. A shopping cart, a bank ledger, and a collaborative document have different conflict semantics.</p>
<h2>5. How CP Systems Work</h2>
<p>CP systems need mechanisms to coordinate operations safely when multiple nodes exist.</p>
<p>This is where consensus, leader election, replicated logs, and quorums become important.</p>
<h3>Consensus</h3>
<p>Consensus is the problem of getting participating nodes to agree on a value or sequence of operations despite supported failures.</p>
<p>Two widely known approaches are the Raft consensus algorithm and the Paxos family of algorithms.</p>
<p>Consensus protocols help systems maintain a consistent history even when nodes fail or communication becomes unreliable.</p>
<h3>Leader election</h3>
<p>Many consensus-based systems use a leader to coordinate writes.</p>
<p>A simplified workflow looks like this:</p>
<ol>
<li>The cluster elects a leader.</li>
<li>Clients send writes to the leader.</li>
<li>The leader replicates entries to other voting nodes.</li>
<li>The protocol determines when entries are committed.</li>
<li>The system acknowledges committed operations.</li>
</ol>
<p>If the leader fails, the remaining nodes may elect a replacement, provided the protocol's election requirements are satisfied.</p>
<p>A timeout does not prove that the old leader crashed. It might merely be isolated or experiencing a long delay. Protocols must therefore prevent an obsolete leader from safely committing conflicting operations.</p>
<h3>Replicated logs</h3>
<p>A replicated log records operations in a defined order.</p>
<p>For example:</p>
<ol>
<li>Reserve inventory for order A.</li>
<li>Decrease available inventory.</li>
<li>Confirm the reservation.</li>
</ol>
<p>Replicating the log helps nodes agree on the order in which operations are applied.</p>
<p>The exact guarantees depend on the consensus protocol and the surrounding transaction logic.</p>
<h3>Quorum</h3>
<p>A quorum is the required set of participating nodes for an operation or decision.</p>
<p>For a standard majority-based voting configuration with \(N\) voting nodes, the majority is:</p>
<p>[ Q = \left\lfloor \frac{N}{2} \right\rfloor + 1 ]</p>
<p>Examples:</p>
<table>
<thead>
<tr>
<th align="right">Voting nodes</th>
<th align="right">Majority quorum</th>
<th align="right">Failures tolerated while retaining a majority</th>
</tr>
</thead>
<tbody><tr>
<td align="right">3</td>
<td align="right">2</td>
<td align="right">1</td>
</tr>
<tr>
<td align="right">5</td>
<td align="right">3</td>
<td align="right">2</td>
</tr>
<tr>
<td align="right">7</td>
<td align="right">4</td>
<td align="right">3</td>
</tr>
</tbody></table>
<p>These failure counts assume a standard majority-consensus cluster and otherwise suitable operating conditions.</p>
<p>If a three-node cluster loses one node, the remaining two can still form a majority. If it splits into two groups of one and two nodes, the group of two may continue while the isolated node cannot independently commit majority-based writes.</p>
<p>This prevents both sides from independently committing conflicting histories under the protocol's safety rules.</p>
<h3>Split brain</h3>
<p>Split brain occurs when multiple parts of a system act as though they are the authoritative leader or owner of a resource.</p>
<p>Consensus protocols use terms, voting rules, and commit rules to protect their replicated state.</p>
<p>When external systems are involved, fencing tokens or monotonically increasing epochs can help prevent an obsolete leader from continuing to modify a resource after ownership has changed.</p>
<p>A leader-election timeout by itself is not sufficient protection against every form of split brain.</p>
<h2>6. Synchronous vs Asynchronous Replication</h2>
<p>Replication mode affects latency, durability, and availability.</p>
<h3>Synchronous replication</h3>
<p>The system waits for acknowledgments from the required replicas before confirming a write.</p>
<p>Advantages:</p>
<ul>
<li>Can improve durability and consistency guarantees.</li>
<li>Reduces the window in which an acknowledged write exists only on one node.</li>
</ul>
<p>Trade-offs:</p>
<ul>
<li>Higher write latency.</li>
<li>A failed or unreachable required replica may prevent writes from completing.</li>
</ul>
<p>The exact guarantees depend on which acknowledgments are required and what the storage system considers durable.</p>
<h3>Asynchronous replication</h3>
<p>The system acknowledges a write before every replica has received it.</p>
<p>Advantages:</p>
<ul>
<li>Lower latency in many configurations.</li>
<li>The primary can continue accepting writes while some replicas lag.</li>
</ul>
<p>Trade-offs:</p>
<ul>
<li>Replication lag is possible.</li>
<li>Certain failures can lose acknowledged writes if they were not durably replicated elsewhere.</li>
</ul>
<p>Synchronous replication does not automatically make an entire database CP, and asynchronous replication does not automatically make an entire database AP. The complete protocol and failure behavior determine the guarantees.</p>
<h2>7. Quorum-Based Reads and Writes</h2>
<p>Some distributed databases let clients configure how many replicas must participate in reads and writes.</p>
<p>Let:</p>
<ul>
<li>\(N\) = total replicas in the relevant replica set.</li>
<li>\(R\) = replicas required for a read.</li>
<li>\(W\) = replicas required for a write.</li>
</ul>
<p>A commonly used overlap condition is:</p>
<p>[ R + W &gt; N ]</p>
<p>This ensures that the read set and write set must overlap, assuming they refer to the same replica set.</p>
<h3>Example: Three replicas</h3>
<p>Suppose \(N = 3\).</p>
<p><strong>Configuration A: \(R = 2,\ W = 2\)</strong></p>
<p>The read and write quorums overlap. A read can contact two replicas, and a write requires two acknowledgments.</p>
<p><strong>Configuration B: \(R = 1,\ W = 1\)</strong></p>
<p>Both operations may be fast, but a read can contact a replica that has not received the latest update.</p>
<p><strong>Configuration C: \(R = 1,\ W = 3\)</strong></p>
<p>Every replica must acknowledge a write, but reads need only one replica. This creates a high write-availability cost while still requiring care around replica freshness and failure behavior.</p>
<p>The critical caveat is that <strong>quorum overlap alone does not guarantee linearizability</strong>. Version ordering, concurrent writes, conflict resolution, read protocols, and commit semantics also matter.</p>
<p>For example, Cassandra exposes consistency levels such as <code>ONE</code>, <code>QUORUM</code>, and <code>ALL</code>. These provide tunable requirements, but a <code>QUORUM</code> setting should not automatically be treated as equivalent to a Raft-based consensus protocol.</p>
<h2>8. Strong Consistency vs Eventual Consistency</h2>
<p>CAP is easier to understand when separated from the broader family of consistency models.</p>
<h3>Strong consistency</h3>
<p>A strong consistency guarantee requires reads to behave according to a defined ordering of writes. Linearizability is one particularly useful model.</p>
<p>It is important for operations where users must not observe invalid intermediate states.</p>
<p>Examples:</p>
<ul>
<li>Checking whether a unique username is already taken.</li>
<li>Committing a financial ledger operation.</li>
<li>Reserving the final available seat.</li>
</ul>
<h3>Eventual consistency</h3>
<p>Eventual consistency means that, under the model's assumptions, replicas converge if updates stop and reconciliation succeeds.</p>
<p>It does not mean every read is fresh. It does not guarantee that every conflict is resolved correctly for the business. And it does not automatically mean that no data can be lost.</p>
<p>Examples that may tolerate eventual consistency:</p>
<ul>
<li>Like counts.</li>
<li>Cached product descriptions.</li>
<li>Search indexes.</li>
<li>Recommendation features.</li>
<li>Some analytics dashboards.</li>
</ul>
<h3>Other useful consistency guarantees</h3>
<p><strong>Read-after-write consistency:</strong> A client sees its own successful update in subsequent reads.</p>
<p><strong>Causal consistency:</strong> Operations that are causally related are observed in a compatible order.</p>
<p><strong>Monotonic reads:</strong> Once a client has observed a newer version, subsequent reads do not move backward to an older version.</p>
<p><strong>Bounded staleness:</strong> Reads are constrained by a defined age or version lag, if the system actually enforces that bound.</p>
<p>These models solve different problems and should not be treated as a single simple ladder of guarantees.</p>
<h3>CAP consistency is not ACID consistency</h3>
<p>This distinction is especially important in interviews.</p>
<p>In CAP, consistency generally refers to linearizability: operations behave as though they happened in a valid real-time order.</p>
<p>In ACID transactions, consistency means a transaction preserves the application's defined invariants, such as not allowing an account balance to violate a business constraint.</p>
<p>The terms are related to correctness, but they mean different things.</p>
<h2>9. Production Example: Designing an Inventory Service</h2>
<p>Imagine you are building an e-commerce inventory service across multiple regions.</p>
<p>The inventory for a product is one unit. Two customers in different regions try to purchase it at almost the same time.</p>
<p>A naive AP approach might allow each region to decrement its local copy. Both purchases could succeed, producing an oversell.</p>
<p>A safer design begins by identifying the business invariant:</p>
<p><strong>A single unit must not be allocated to two different orders.</strong></p>
<p>One possible design is to route reservations through a strongly coordinated inventory service or transactional datastore.</p>
<p>A reservation might follow this workflow:</p>
<ol>
<li>Receive the request with a unique idempotency key.</li>
<li>Check whether the request has already been processed.</li>
<li>Atomically verify that inventory is available and reserve one unit.</li>
<li>Persist the reservation and its state.</li>
<li>Return the result.</li>
<li>Publish downstream events using a reliable delivery pattern.</li>
</ol>
<p>If the service cannot establish the required coordination, it should not confirm a reservation that could violate the invariant.</p>
<p>Other operations can have different requirements. Product descriptions might be served from a cache, while search indexes update asynchronously.</p>
<p>This is how real systems often work: <strong>the same application can use different consistency strategies for different operations.</strong></p>
<h3>Idempotency matters</h3>
<p>A client may time out after submitting a reservation. It retries because it does not know whether the first request succeeded.</p>
<p>Without idempotency, the retry could reserve inventory twice.</p>
<p>An idempotency key lets the service recognize repeated attempts representing the same logical operation and return the appropriate stored result.</p>
<p>A plain increment is not idempotent. Repeating it twice changes the value twice. For event processing, stable event identifiers and deduplication can help avoid applying the same logical event more than once.</p>
<p>Idempotency does not replace transactions or coordination, but it is essential for robust retries.</p>
<h2>10. Failure Detection, Recovery, and Monitoring</h2>
<p>A distributed system must be designed for failures rather than just normal operation.</p>
<h3>Timeouts and heartbeats</h3>
<p>Heartbeats and timeouts help nodes detect that a peer may be unavailable.</p>
<p>However, a slow network and a crashed machine can look similar from the observer's perspective.</p>
<p>Failure detection is therefore often based on suspicion rather than certainty. Consensus protocols must remain safe even when failure suspicions are mistaken.</p>
<h3>Graceful degradation</h3>
<p>Not every feature needs to fail in the same way.</p>
<p>During a partition, a shopping application might:</p>
<ul>
<li>Continue displaying cached product descriptions.</li>
<li>Serve recommendations from previously computed results.</li>
<li>Disable checkout if a safe inventory reservation cannot be completed.</li>
<li>Queue noncritical notifications for later delivery.</li>
</ul>
<p>This allows the application to preserve safe functionality without violating important business invariants.</p>
<h3>Recovery and reconciliation</h3>
<p>When communication recovers, the system must determine which updates are missing, whether conflicts exist, and how the final state should be established.</p>
<p>A recovery process may include:</p>
<ol>
<li>Identifying stale replicas.</li>
<li>Comparing versions or logs.</li>
<li>Transferring missing updates.</li>
<li>Resolving conflicts according to defined rules.</li>
<li>Verifying convergence and business invariants.</li>
</ol>
<p>A system that resumes accepting traffic but never repairs divergent replicas is not fully recovered.</p>
<h3>What should be monitored?</h3>
<p>For AP-oriented systems:</p>
<ul>
<li>Replication lag.</li>
<li>Conflict rates.</li>
<li>Hinted-handoff backlog, where supported.</li>
<li>Anti-entropy and repair duration.</li>
<li>Failed or duplicated operations.</li>
<li>Time to converge after a partition.</li>
</ul>
<p>For CP-oriented systems:</p>
<ul>
<li>Quorum availability.</li>
<li>Leader-election frequency.</li>
<li>Commit latency.</li>
<li>Replication and log-commit lag.</li>
<li>Failed writes and coordination timeouts.</li>
<li>Time spent without a usable majority.</li>
</ul>
<p>These metrics make consistency and availability problems observable rather than leaving them hidden until customers report incorrect behavior.</p>
<h2>11. Beyond CAP: The PACELC Model</h2>
<p>CAP describes the consistency-versus-availability trade-off during a partition, but it does not fully describe the latency choices systems make when the network is healthy.</p>
<p>PACELC extends the discussion:</p>
<ul>
<li><strong>If Partitioned (P):</strong> choose between Availability (A) and Consistency (C).</li>
<li><strong>Else (E):</strong> choose between Latency (L) and Consistency (C).</li>
</ul>
<p>In other words, even when there is no partition, waiting for remote coordination can increase latency.</p>
<p>For a globally distributed application, the system may need to wait for messages across regions to provide stronger guarantees. A lower-latency design may allow some reads to use older data.</p>
<p>This makes PACELC useful when comparing architectures for multi-region applications.</p>
<h2>12. Common Distributed Systems Mistakes</h2>
<p>Here are mistakes worth avoiding in both implementation and interviews.</p>
<p><strong>Mistake 1: Saying an AP system can never lose data.</strong></p>
<p>AP describes a consistency-availability trade-off during partitions. Durability depends on replication, persistence, acknowledgments, and recovery mechanisms.</p>
<p><strong>Mistake 2: Saying a CP system is always unavailable during normal operation.</strong></p>
<p>A CP-oriented system can provide both strong consistency and availability when the required coordination works. Availability may be sacrificed for certain requests during a partition.</p>
<p><strong>Mistake 3: Assuming a timeout proves that a server crashed.</strong></p>
<p>The server may be alive but unreachable or slow.</p>
<p><strong>Mistake 4: Assuming quorum automatically guarantees linearizability.</strong></p>
<p>Quorum intersection helps, but the protocol must also handle ordering, versions, concurrent writes, and commitment correctly.</p>
<p><strong>Mistake 5: Retrying every operation without idempotency.</strong></p>
<p>Retries can duplicate payments, reservations, or other non-idempotent operations.</p>
<p><strong>Mistake 6: Assuming last-write-wins is always safe.</strong></p>
<p>A timestamp-based winner can silently discard valid concurrent updates.</p>
<p><strong>Mistake 7: Ignoring recovery.</strong></p>
<p>A system must repair divergence and verify correctness after connectivity returns.</p>
<p><strong>Mistake 8: Choosing a database before understanding the business invariant.</strong></p>
<p>First decide what must never go wrong. Then choose the consistency and transaction guarantees needed to protect it.</p>
<h2>13. A Practical Design Checklist</h2>
<p>Before implementing a distributed backend service, ask the following questions.</p>
<h3>For an AP-oriented design</h3>
<ul>
<li>Which reads can tolerate stale data?</li>
<li>Can different regions accept conflicting writes?</li>
<li>How are conflicts resolved?</li>
<li>Are retries idempotent?</li>
<li>How are updates replicated and persisted?</li>
<li>How does the system repair divergent replicas?</li>
<li>Can the team measure lag, conflicts, and convergence?</li>
<li>What happens during and after a network partition?</li>
</ul>
<h3>For a CP-oriented design</h3>
<ul>
<li>Which operations require strong consistency?</li>
<li>What protocol coordinates those operations?</li>
<li>How many voting nodes are deployed?</li>
<li>What happens when the cluster loses its majority?</li>
<li>How are leaders elected and obsolete leaders fenced off?</li>
<li>How are transactions, retries, and timeouts handled?</li>
<li>What are the latency costs of coordination?</li>
<li>How is recovery tested after node or network failure?</li>
</ul>
<p>The answers should come from application requirements, not from a general preference for one architecture.</p>
<h2>14. Interview Questions to Test Your Understanding</h2>
<p>Use these questions to check whether you can apply the concepts rather than merely define them.</p>
<h3>Fundamentals</h3>
<ol>
<li>What is a distributed system, and why is replication useful?</li>
<li>What is replication lag?</li>
<li>What is a network partition?</li>
<li>What exactly does the CAP theorem state?</li>
<li>What does availability mean in the formal CAP model?</li>
</ol>
<h3>Consistency</h3>
<ol start="6">
<li>How does linearizability differ from eventual consistency?</li>
<li>What is read-after-write consistency?</li>
<li>How is CAP consistency different from ACID consistency?</li>
<li>When would bounded staleness be useful?</li>
<li>Can a system provide strong consistency when the network is healthy?</li>
</ol>
<h3>AP architecture</h3>
<ol start="11">
<li>How does gossip help distributed systems?</li>
<li>What are hinted handoff, anti-entropy, and read repair?</li>
<li>How would you resolve conflicting updates from two regions?</li>
<li>What is a CRDT, and when would it be useful?</li>
<li>Why does AP not automatically guarantee durability?</li>
</ol>
<h3>CP architecture and quorums</h3>
<ol start="16">
<li>What problem does consensus solve?</li>
<li>How does leader election work?</li>
<li>Why do majority-based systems need a quorum?</li>
<li>How many failures can a standard five-node majority cluster tolerate while retaining a majority?</li>
<li>Why is quorum overlap alone insufficient to guarantee linearizability?</li>
<li>What is split brain, and how can fencing tokens help?</li>
</ol>
<h3>Production design</h3>
<ol start="22">
<li>How would you prevent two customers from buying the last available item?</li>
<li>How would you prevent duplicate payments after retries?</li>
<li>What happens when a CP-oriented cluster loses its majority?</li>
<li>What is the difference between synchronous and asynchronous replication?</li>
<li>How would you detect and repair divergent replicas?</li>
<li>What does PACELC add to the CAP discussion?</li>
<li>How would you decide which features of an e-commerce system can tolerate eventual consistency?</li>
</ol>
<h2>Conclusion</h2>
<p>The CAP theorem is not a rule that every database permanently belongs to one of two categories. It is a way to reason about the guarantees a distributed system can provide when communication fails.</p>
<p>AP-oriented designs can preserve responsiveness by allowing temporary divergence, provided the application has appropriate reconciliation and durability mechanisms.</p>
<p>CP-oriented designs protect consistency by coordinating operations and refusing or delaying requests that cannot safely proceed during a partition.</p>
<p>Quorums, consensus, replication modes, idempotency, conflict resolution, and repair mechanisms are the tools that turn these trade-offs into real system designs.</p>
<p>The most useful question is not simply, "Should I choose AP or CP?"</p>
<p>It is:</p>
<p><strong>"What must remain correct when the network fails, and which operations can safely continue?"</strong></p>
<p>Once that question is answered, the architecture becomes much easier to reason about.</p>
]]></content:encoded></item></channel></rss>