<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Patterns on Krishnatejaswi S</title>
    <link>https://KTS-o7.github.io/bear/tags/patterns/</link>
    <description>Recent content in Patterns on Krishnatejaswi S</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <managingEditor>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</managingEditor>
    <webMaster>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</webMaster>
    <copyright>Krishnatejaswi S</copyright>
    <lastBuildDate>Sun, 03 May 2026 00:00:00 +0530</lastBuildDate>
    <atom:link href="https://KTS-o7.github.io/bear/tags/patterns/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>The Bulkhead Pattern: Isolating Failures Before They Spread</title>
      <link>https://KTS-o7.github.io/bear/systems/bulkhead_pattern/</link>
      <pubDate>Sun, 03 May 2026 00:00:00 +0530</pubDate><author>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</author>
      <guid>https://KTS-o7.github.io/bear/systems/bulkhead_pattern/</guid>
      <description>&lt;p&gt;Circuit breakers stop you from hammering a failing downstream service. Bulkheads stop a failing downstream service from consuming all the resources you have &amp;ndash; even if you&amp;rsquo;re not hammering it.&lt;/p&gt;&#xA;&lt;p&gt;The name comes from ship design. A ship&amp;rsquo;s hull is divided into watertight compartments. One compartment floods, the others stay dry. The ship stays afloat. Without bulkheads, one breach sinks everything.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-problem-without-bulkheads&#34;&gt;The Problem Without Bulkheads&lt;/h2&gt;&#xA;&lt;p&gt;Imagine a service that calls three downstream APIs &amp;ndash; payment, inventory, and recommendations. You have a thread pool with 100 threads handling all outbound requests.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Circuit breakers stop you from hammering a failing downstream service. Bulkheads stop a failing downstream service from consuming all the resources you have &ndash; even if you&rsquo;re not hammering it.</p>
<p>The name comes from ship design. A ship&rsquo;s hull is divided into watertight compartments. One compartment floods, the others stay dry. The ship stays afloat. Without bulkheads, one breach sinks everything.</p>
<h2 id="the-problem-without-bulkheads">The Problem Without Bulkheads</h2>
<p>Imagine a service that calls three downstream APIs &ndash; payment, inventory, and recommendations. You have a thread pool with 100 threads handling all outbound requests.</p>
<p>The recommendations API starts responding slowly. Requests pile up waiting. Threads get tied up waiting for responses that take 10 seconds instead of 100ms. Within minutes all 100 threads are occupied waiting on recommendations. Now payment requests and inventory requests can&rsquo;t get a thread either. Your service is down for all three functions because one non-critical API got slow.</p>
<p>A circuit breaker would help here once the recommendations API is actually failing &ndash; but slow isn&rsquo;t failing. The requests are completing, just slowly. The circuit never trips. The thread pool still drains.</p>
<h2 id="the-fix-separate-resource-pools">The Fix: Separate Resource Pools</h2>
<p>Partition your resources so that one downstream dependency can only consume its own allocation.</p>
<p>Instead of one pool of 100 threads, you have three pools:</p>
<ul>
<li>Payment: 40 threads</li>
<li>Inventory: 40 threads</li>
<li>Recommendations: 20 threads</li>
</ul>
<p>The recommendations API goes slow. It consumes all 20 of its threads. Payment and inventory are unaffected &ndash; they have their own pools. The non-critical dependency is isolated in its own compartment.</p>
<h2 id="two-implementation-styles">Two Implementation Styles</h2>
<p><strong>Thread pool isolation</strong> is what Hystrix made popular. Each downstream call goes through a dedicated thread pool. Callers block on their own pool, not a shared one. Overhead: context switches between the caller thread and the pool thread for every call.</p>
<p><strong>Semaphore isolation</strong> is lighter. Instead of separate thread pools, you put a semaphore in front of each dependency with a max concurrent count. Callers execute on their own thread but must acquire the semaphore first. No thread switching overhead. The tradeoff: if the call actually blocks the thread (synchronous I/O), you&rsquo;re still tying up the caller&rsquo;s thread. Better for fast calls, worse for slow ones.</p>
<p>For services making lots of downstream calls with variable latency, thread pool isolation is generally safer. The overhead is real but the isolation is stronger.</p>
<h2 id="combining-with-circuit-breakers">Combining with Circuit Breakers</h2>
<p>Bulkheads and circuit breakers are complementary, not alternatives. They protect against different failure modes:</p>
<ul>
<li><strong>Bulkhead</strong>: limits resource consumption from a slow or partially degraded dependency</li>
<li><strong>Circuit breaker</strong>: stops calls entirely when a dependency crosses a failure threshold</li>
</ul>
<p>The typical pattern is both together. The bulkhead caps how many threads/connections one dependency can consume. The circuit breaker trips when that dependency starts returning errors. Between the two, you handle the full range of failure behavior from &ldquo;slightly slow&rdquo; to &ldquo;completely down.&rdquo;</p>
<p>Netflix Hystrix combined both in a single abstraction. Resilience4j does the same. In a service mesh like Istio, connection pool settings are the bulkhead and outlier detection is the circuit breaker &ndash; same ideas, configured at the mesh level rather than the application level.</p>
<h2 id="what-to-size-the-pools-at">What to Size the Pools At</h2>
<p>This is the hard part. Too small and you&rsquo;re rejecting valid traffic. Too large and the isolation breaks down.</p>
<p>A reasonable starting point: look at your normal concurrency for each dependency, add headroom for spikes, and set the max at roughly 2x your normal peak. Monitor pool utilization in steady state &ndash; if the recommendations pool is regularly at 18/20, you either need to size up or your recommendations calls are too slow.</p>
<p>Bulkheads don&rsquo;t fix slow dependencies. They contain the blast radius while you do.</p>
]]></content:encoded>
    </item>
    <item>
      <title>The Saga Outbox Pattern: Reliable Event Delivery Without Two-Phase Commit</title>
      <link>https://KTS-o7.github.io/bear/systems/saga_outbox_pattern/</link>
      <pubDate>Sun, 03 May 2026 00:00:00 +0530</pubDate><author>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</author>
      <guid>https://KTS-o7.github.io/bear/systems/saga_outbox_pattern/</guid>
      <description>&lt;p&gt;The Saga pattern gives you distributed transactions without cross-service locks. Each step publishes an event when it completes, triggering the next step. But there&amp;rsquo;s a gap in the naive implementation: publishing an event and updating your local database are two separate operations. If your service crashes between them, you get a committed database write with no event published, or an event published with no matching database write. The saga is now in an inconsistent state.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The Saga pattern gives you distributed transactions without cross-service locks. Each step publishes an event when it completes, triggering the next step. But there&rsquo;s a gap in the naive implementation: publishing an event and updating your local database are two separate operations. If your service crashes between them, you get a committed database write with no event published, or an event published with no matching database write. The saga is now in an inconsistent state.</p>
<p>The outbox pattern closes that gap.</p>
<h2 id="the-dual-write-problem">The Dual-Write Problem</h2>
<p>Say the flight reservation step succeeds. You update your database to mark the reservation as confirmed and then publish a <code>FlightReserved</code> event to your message broker. These are two operations against two different systems. There&rsquo;s no way to make them atomic without 2PC &ndash; which is exactly what sagas are trying to avoid.</p>
<p>If you write to the database first and then crash before publishing, the event is never delivered. The hotel reservation step never starts. The saga stalls. Depending on your monitoring, this might sit undetected for a while.</p>
<p>If you publish first and then crash before writing to the database, you&rsquo;ve told the hotel step to proceed even though the flight reservation isn&rsquo;t durably recorded. The flight service comes back up with no record of the reservation. You&rsquo;ve introduced a phantom.</p>
<h2 id="the-outbox">The Outbox</h2>
<p>The fix: write the event to an outbox table in the same database, in the same local transaction as your state update. Then have a separate process relay events from the outbox to the message broker.</p>





<pre tabindex="0"><code>BEGIN TRANSACTION
  UPDATE flights SET status = &#39;reserved&#39; WHERE id = ?
  INSERT INTO outbox (event_type, payload, created_at)
    VALUES (&#39;FlightReserved&#39;, &#39;{&#34;booking_id&#34;: 123, ...}&#39;, NOW())
COMMIT</code></pre><p>The state change and the event are now atomic with respect to the database. Either both are committed or neither is. The outbox row is durable.</p>
<p>A separate relay process &ndash; sometimes called the outbox processor or message relay &ndash; polls the outbox table, publishes each event to the message broker, and marks the row as processed once delivery is confirmed.</p>





<pre tabindex="0"><code>Relay process:
  SELECT * FROM outbox WHERE processed_at IS NULL ORDER BY created_at LIMIT 100
  FOR EACH row:
    publish to message broker
    UPDATE outbox SET processed_at = NOW() WHERE id = row.id</code></pre><h2 id="at-least-once-delivery">At-Least-Once Delivery</h2>
<p>The relay delivers at-least-once. If the relay crashes after publishing but before marking the row as processed, it will publish the same event again on restart. Downstream consumers have to be idempotent &ndash; processing the same event twice should produce the same result as processing it once.</p>
<p>This is a real design constraint. Your hotel reservation step needs to check whether it already processed a given <code>FlightReserved</code> event before creating a new reservation. Deduplication is typically done with a unique constraint on the event ID or booking ID in the receiving service&rsquo;s database.</p>
<p>At-least-once with idempotent consumers is the standard contract in event-driven systems. Exactly-once is theoretically possible with additional coordination but practically rare &ndash; most systems accept at-least-once and invest in idempotency instead.</p>
<h2 id="change-data-capture-as-an-alternative-relay">Change Data Capture as an Alternative Relay</h2>
<p>Polling the outbox table works but adds load to the database. An alternative is Change Data Capture (CDC): instead of a polling process, you read directly from the database replication log.</p>
<p>Tools like Debezium connect to PostgreSQL&rsquo;s logical replication stream or MySQL&rsquo;s binlog and emit a change event for every row insert. Point Debezium at the outbox table and it publishes events automatically, with no polling query needed.</p>
<p>CDC-based relay has lower overhead and lower latency. The tradeoff: it&rsquo;s another piece of infrastructure to run and operate. For smaller deployments, polling is simpler. For high-throughput systems where polling lag or database load is a concern, CDC is worth it.</p>
<h2 id="saga-state-persistence">Saga State Persistence</h2>
<p>Related to the outbox is the question of where saga state itself lives.</p>
<p>The orchestrator driving the saga needs to know the current step, which steps have completed, and what the compensating actions are if something fails. Two common approaches:</p>
<p><strong>Dedicated saga store</strong>: the orchestrator writes saga state to its own database table after each step. Simple, queryable, easy to inspect when debugging a stuck saga. The outbox pattern applies here too &ndash; saga state update and event publish happen in the same transaction.</p>
<p><strong>Event sourcing</strong>: saga state is derived from the event log itself. There&rsquo;s no explicit state table &ndash; you reconstruct the saga&rsquo;s current position by replaying events. More complex to implement but gives you a full audit trail of everything that happened and an easy path to replaying failed sagas.</p>
<p>Most teams start with a dedicated saga store and move to event sourcing only if they need the audit trail or replay capability.</p>
<h2 id="what-fails-in-compensation">What Fails in Compensation</h2>
<p>One more gap worth naming: compensating transactions can fail too.</p>
<p>You cancel the hotel reservation as part of rolling back a failed booking. The cancel call times out. Now your saga is stuck in a partially compensated state &ndash; the flight is cancelled but the hotel reservation is still live.</p>
<p>The standard answer is retry with backoff. Compensating transactions need to be idempotent for the same reason forward steps do. The outbox pattern helps here too &ndash; compensation requests go through the same outbox mechanism, so they&rsquo;re durably enqueued even if the downstream service is temporarily unavailable.</p>
<p>If compensation keeps failing after retries, the saga is in a &ldquo;saga failure&rdquo; state that requires human intervention or a dead-letter queue. Building an operator UI for stuck sagas is not glamorous work, but it&rsquo;s necessary in any production saga implementation.</p>
<p>The outbox pattern doesn&rsquo;t make sagas simple. It removes the dual-write problem and gives you reliable event delivery &ndash; which turns a fundamentally broken approach into one that&rsquo;s merely complex.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Circuit Breakers: The Pattern That Stops Cascading Failures</title>
      <link>https://KTS-o7.github.io/bear/systems/circuit_breakers_distributed_systems/</link>
      <pubDate>Sat, 02 May 2026 00:00:00 +0530</pubDate><author>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</author>
      <guid>https://KTS-o7.github.io/bear/systems/circuit_breakers_distributed_systems/</guid>
      <description>&lt;p&gt;The payment service starts timing out. The fraud detection API it depends on is slow. Your retry logic kicks in &amp;ndash; which makes sense, retries are good &amp;ndash; but now every incoming request is spawning 3x the load on a service that was already struggling. The fraud service falls over completely. Now the entire checkout flow is dead.&lt;/p&gt;&#xA;&lt;p&gt;This is a cascading failure. One slow dependency turned into a full outage because the upstream service kept hammering it instead of stopping.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The payment service starts timing out. The fraud detection API it depends on is slow. Your retry logic kicks in &ndash; which makes sense, retries are good &ndash; but now every incoming request is spawning 3x the load on a service that was already struggling. The fraud service falls over completely. Now the entire checkout flow is dead.</p>
<p>This is a cascading failure. One slow dependency turned into a full outage because the upstream service kept hammering it instead of stopping.</p>
<h2 id="the-pattern">The Pattern</h2>
<p>Circuit breakers fix this by failing fast. The name comes from electrical circuit breakers: when the current exceeds a threshold, the breaker trips and cuts power before anything burns. Same idea here.</p>
<p>The circuit has three states:</p>
<p><strong>Closed</strong> &ndash; normal operation. Requests go through, failures are counted. When failures cross a configured threshold within a time window, the circuit trips.</p>
<p><strong>Open</strong> &ndash; the circuit has tripped. Requests immediately return an error without calling the downstream service at all. No network round-trips, no timeouts, no extra load on a service that&rsquo;s already struggling. After a cooldown period, the circuit moves to half-open.</p>
<p><strong>Half-open</strong> &ndash; one test request is allowed through. If it succeeds, the circuit closes and normal operation resumes. If it fails, the circuit trips back to open and the cooldown resets.</p>
<p>The key insight is that sometimes the most useful thing you can do is stop trying. Retrying a failing service doesn&rsquo;t help it recover. Backing off does.</p>
<h2 id="where-youll-find-it">Where You&rsquo;ll Find It</h2>
<p>Netflix Hystrix made this pattern famous, but it&rsquo;s now table stakes in most infrastructure:</p>
<ul>
<li>Istio has it built into the service mesh at the sidecar level &ndash; no application code needed</li>
<li>Resilience4j for Java</li>
<li>Polly for .NET</li>
<li>Most Redis clients ship with circuit breaking on connection failures</li>
</ul>
<p>The mesh-level implementations are interesting because they work across languages and don&rsquo;t require every service team to implement the pattern themselves. You configure thresholds in the mesh and it handles the state machine.</p>
<h2 id="the-gotcha-timeout-ordering">The Gotcha: Timeout Ordering</h2>
<p>There&rsquo;s one configuration mistake that makes circuit breakers useless: setting your request timeout longer than your circuit breaker&rsquo;s failure window.</p>
<p>If your timeout is 30 seconds but your circuit breaker trips after 5 failures within 10 seconds &ndash; and each failure takes 25 seconds to time out &ndash; you&rsquo;ll accumulate only one or two failures in any 10-second window. The circuit never trips because failures arrive too slowly to hit the threshold, even while the system is drowning in slow requests.</p>
<p>The rule: timeouts must be shorter than the failure window. If your breaker watches a 10-second window and trips at 5 failures, your per-request timeout needs to be well under 2 seconds. Otherwise the breaker is just decorative.</p>
]]></content:encoded>
    </item>
    <item>
      <title>The Saga Pattern: Distributed Transactions Without the Lock</title>
      <link>https://KTS-o7.github.io/bear/systems/saga_pattern/</link>
      <pubDate>Sat, 02 May 2026 00:00:00 +0530</pubDate><author>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</author>
      <guid>https://KTS-o7.github.io/bear/systems/saga_pattern/</guid>
      <description>&lt;p&gt;Two-phase commit gives you distributed atomicity. It also gives you a coordinator that can stall your entire system if it crashes at the wrong moment, and locks held across multiple services for however long the transaction takes. For short, internal transactions that&amp;rsquo;s manageable. For a trip booking flow that touches a flight service, a hotel service, and a payment service &amp;ndash; each owned by a different team, each potentially slow &amp;ndash; it&amp;rsquo;s not.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Two-phase commit gives you distributed atomicity. It also gives you a coordinator that can stall your entire system if it crashes at the wrong moment, and locks held across multiple services for however long the transaction takes. For short, internal transactions that&rsquo;s manageable. For a trip booking flow that touches a flight service, a hotel service, and a payment service &ndash; each owned by a different team, each potentially slow &ndash; it&rsquo;s not.</p>
<p>The Saga pattern is the alternative. You give up true atomicity in exchange for availability, scalability, and no cross-service locks.</p>
<h2 id="how-it-works">How It Works</h2>
<p>Break one big distributed transaction into a sequence of local transactions, each owned by a single service. Each step publishes an event or sends a message when it completes. If any step fails, run compensating transactions in reverse to undo the completed steps.</p>
<p>The classic example is a trip booking:</p>





<pre tabindex="0"><code>Step 1: Reserve flight    → local tx in Flight Service
Step 2: Reserve hotel     → local tx in Hotel Service
Step 3: Charge card       → local tx in Payment Service</code></pre><p>If step 3 fails:</p>





<pre tabindex="0"><code>FORWARD:   [Flight ✓] → [Hotel ✓] → [Payment ✗]
ROLLBACK:              ← [Cancel Hotel] ← [Cancel Flight]</code></pre><p>No global rollback. No coordinator holding locks across all three services. Each service knows how to undo its own step.</p>
<h2 id="two-flavors">Two Flavors</h2>
<p><strong>Choreography</strong> &ndash; each service listens for events and decides what to do next based on what it hears. No central coordinator. Fully decoupled. Easy to start, and a debugging nightmare once you have more than a handful of services. Tracing a failed saga means reconstructing a sequence of events scattered across multiple event streams.</p>
<p><strong>Orchestration</strong> &ndash; a central saga orchestrator drives the sequence. It tells each service what to do, collects results, and issues compensating calls on failure. Full visibility into saga state. Easier to reason about and monitor. The orchestrator becomes a coordination hub &ndash; not quite a single point of failure if you make it durable, but a complexity center. Most teams end up here in production.</p>
<h2 id="the-tradeoff-you-cant-avoid">The Tradeoff You Can&rsquo;t Avoid</h2>
<p>Sagas trade the &ldquo;I&rdquo; in ACID (isolation) for availability. While a saga is in-flight, intermediate state is visible to other parts of the system. A hotel room might be reserved even though the flight hasn&rsquo;t been confirmed yet. Other services can observe that state.</p>
<p>You have to design for this explicitly:</p>
<ul>
<li><strong>Eventual consistency</strong> &ndash; the system converges to a correct final state, but not atomically</li>
<li><strong>Idempotent compensating transactions</strong> &ndash; if your &ldquo;cancel flight&rdquo; call gets retried twice due to a network hiccup, it must be safe to run twice. Compensation that isn&rsquo;t idempotent will make your failure recovery worse than the original failure.</li>
</ul>
<table>
  <thead>
      <tr>
          <th>Property</th>
          <th>2PC</th>
          <th>Saga</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Atomicity</td>
          <td>Global</td>
          <td>Per-step only</td>
      </tr>
      <tr>
          <td>Isolation</td>
          <td>Full</td>
          <td>Intermediate state visible</td>
      </tr>
      <tr>
          <td>Availability</td>
          <td>Blocks on coordinator failure</td>
          <td>Always makes progress</td>
      </tr>
      <tr>
          <td>Scalability</td>
          <td>Locks across services</td>
          <td>No cross-service locks</td>
      </tr>
  </tbody>
</table>
<h2 id="where-it-shows-up">Where It Shows Up</h2>
<p>Basically every large-scale order or booking flow:</p>
<ul>
<li><strong>Uber</strong> &ndash; ride booking, driver assignment, payment charge, all as separate service transactions</li>
<li><strong>Amazon</strong> &ndash; order placement, inventory reservation, fulfillment, shipping</li>
<li><strong>Netflix</strong> &ndash; subscription management, content licensing flows</li>
</ul>
<p>These systems can&rsquo;t afford to hold cross-service locks for the duration of a user-facing transaction. They accept that intermediate states exist and design their compensating logic carefully.</p>
<p>The saga pattern doesn&rsquo;t make distributed transactions easy. It makes the tradeoffs explicit and gives you a way to build systems that stay available even when individual steps fail.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
