<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Hayden Cordeiro</title>
    <description>The latest articles on Forem by Hayden Cordeiro (@haydencordeiro).</description>
    <link>https://forem.com/haydencordeiro</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F729206%2Fef4bc733-9882-4dfd-ac52-d03c393fd60f.png</url>
      <title>Forem: Hayden Cordeiro</title>
      <link>https://forem.com/haydencordeiro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/haydencordeiro"/>
    <language>en</language>
    <item>
      <title>ELI25: Apache Kafka Quick Notes for Interviews</title>
      <dc:creator>Hayden Cordeiro</dc:creator>
      <pubDate>Sun, 15 Feb 2026 20:23:35 +0000</pubDate>
      <link>https://forem.com/haydencordeiro/eli25-apache-kafka-quick-notes-for-interviews-oph</link>
      <guid>https://forem.com/haydencordeiro/eli25-apache-kafka-quick-notes-for-interviews-oph</guid>
      <description>&lt;p&gt;&lt;strong&gt;Kafka&lt;/strong&gt; was originally built by &lt;strong&gt;LinkedIn&lt;/strong&gt; and later made open source under the &lt;strong&gt;Apache Software Foundation&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Goals&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;High throughput&lt;/li&gt;
&lt;li&gt;Scalable&lt;/li&gt;
&lt;li&gt;Reliable&lt;/li&gt;
&lt;li&gt;Fault-tolerant&lt;/li&gt;
&lt;li&gt;Pub/Sub architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Use Cases&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Messaging&lt;/li&gt;
&lt;li&gt;Data replication&lt;/li&gt;
&lt;li&gt;Middleware logic&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The Architecture&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;At a high level, the flow looks like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Producers&lt;/strong&gt; -----&amp;gt; &lt;strong&gt;Brokers&lt;/strong&gt; (Topics/Partitions) -----&amp;gt; &lt;strong&gt;Consumer Groups&lt;/strong&gt; (Consumers)&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Producers&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;These are client applications that produce (write) data to the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Brokers&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Brokers are daemons (background processes) that run on hardware or virtual machines.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cluster:&lt;/strong&gt; Multiple brokers running together form a Kafka Cluster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; Their primary job is to take messages from producers and store them on disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retention:&lt;/strong&gt; Brokers have a defined retention policy (time-based or size-based). Once the limit is reached, old messages are deleted to make room for new ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Topics&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Topics are logical collections of messages (e.g., &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;customers&lt;/code&gt;, &lt;code&gt;clicks&lt;/code&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can have as many topics as you want.&lt;/li&gt;
&lt;li&gt;Topics are split into &lt;strong&gt;Partitions&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Partitions&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A topic can have one or more partitions. These partitions are distributed across the brokers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example Scenarios:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scenario A:&lt;/strong&gt; 1 Topic (&lt;code&gt;Orders&lt;/code&gt;), 2 Brokers, 2 Partitions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Broker 1:&lt;/em&gt; Holds &lt;code&gt;Orders-Partition-0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Broker 2:&lt;/em&gt; Holds &lt;code&gt;Orders-Partition-1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Scenario B:&lt;/strong&gt; 1 Topic, 3 Brokers, 2 Partitions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Broker 1:&lt;/em&gt; Holds &lt;code&gt;Orders-Partition-0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Broker 2:&lt;/em&gt; Holds &lt;code&gt;Orders-Partition-1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Broker 3:&lt;/em&gt; Unused for this topic.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Scenario C:&lt;/strong&gt; 1 Topic, 1 Broker, 2 Partitions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Broker 1:&lt;/em&gt; Holds both &lt;code&gt;Orders-Partition-0&lt;/code&gt; and &lt;code&gt;Orders-Partition-1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important Note:&lt;/strong&gt; Every message is written to a specific partition of a TOPIC. Each message gets a unique &lt;strong&gt;Offset ID&lt;/strong&gt;. Kafka guarantees ordering &lt;strong&gt;only within a partition&lt;/strong&gt;, not across the entire topic.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Consumer Groups&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Consumers&lt;/strong&gt; are the applications reading data from Kafka.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They sit in logical groupings called &lt;strong&gt;Consumer Groups&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Consumers can read from one or more partitions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Scaling Rule:&lt;/strong&gt;&lt;br&gt;
If you have &lt;strong&gt;more Consumers than Partitions&lt;/strong&gt;, the extra consumers will sit &lt;strong&gt;idle&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Example:&lt;/em&gt; 2 Partitions, 3 Consumers ---&amp;gt; Consumer 1 reads Partition 1, Consumer 2 reads Partition 2, &lt;strong&gt;Consumer 3 sits idle.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Offset Management:&lt;/strong&gt;&lt;br&gt;
Consumers track the last message they read. If a consumer crashes, the group rebalances. A new consumer picks up the partition and resumes from the last committed &lt;strong&gt;Offset&lt;/strong&gt; (the unique ID mentioned earlier).&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Handling Failure (Fault Tolerance)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Understanding the "happy path" is great, but interviews often focus on failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consumer Failure:&lt;/strong&gt;&lt;br&gt;
Straightforward. The new consumer looks up the last committed offset and continues reading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Broker Failure:&lt;/strong&gt;&lt;br&gt;
Since messages are stored on disk inside the broker, what happens if the broker dies? Do we lose data?&lt;br&gt;
&lt;strong&gt;No.&lt;/strong&gt; This is where the &lt;strong&gt;Replication Factor&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;Kafka replicates partitions across different brokers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; &lt;code&gt;Orders&lt;/code&gt; Topic, 2 Brokers, Replication Factor of 2.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Broker 1:&lt;/strong&gt; Holds &lt;code&gt;Partition 0 (Leader)&lt;/code&gt;, &lt;code&gt;Partition 1 (Replica)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broker 2:&lt;/strong&gt; Holds &lt;code&gt;Partition 1 (Leader)&lt;/code&gt;, &lt;code&gt;Partition 0 (Replica)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;If Broker 2 crashes, Broker 1 typically takes over as the Leader for Partition 1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When does replication happen?&lt;/strong&gt;&lt;br&gt;
When a message is written to the Leader partition, it is immediately relayed to the follower replicas.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Producer Acknowledgement (ACKS)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The producer sends a message to the leader partition. The leader writes it, replicates it, and sends an acknowledgment (ACK) back. You can configure how strict this is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;acks=0&lt;/code&gt; &lt;strong&gt;(Fire and Forget):&lt;/strong&gt; Producer sends data and doesn't wait for a response. Fastest, but risk of data loss.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;acks=1&lt;/code&gt; &lt;strong&gt;(Leader Ack):&lt;/strong&gt; Producer waits for the Leader to confirm it wrote the message.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;acks=all&lt;/code&gt; &lt;strong&gt;(Strongest):&lt;/strong&gt; Producer waits for the Leader &lt;strong&gt;AND&lt;/strong&gt; all in-sync replicas to confirm. Safest, but highest latency.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Callouts&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Retention is NOT "One-in-One-out"&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A common misconception is that Kafka deletes messages individually (e.g., as soon as a new message arrives, an old one is deleted).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reality:&lt;/strong&gt; Kafka deletes entire &lt;strong&gt;Log Segments&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How it works:&lt;/strong&gt; Kafka writes messages to a file called a "segment." When that file gets full (e.g., 1GB), it closes it and starts a new one. A background process checks the &lt;em&gt;closed&lt;/em&gt; segments. If a segment is older than the retention period (e.g., 7 days), the &lt;strong&gt;entire file&lt;/strong&gt; is deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Replication is PULL, not PUSH&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;How do followers stay in sync with the leader?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reality:&lt;/strong&gt; The Leader does not "push" data to followers. Followers &lt;strong&gt;PULL&lt;/strong&gt; (fetch) data from the Leader.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "Magic" of the Fetch Request:&lt;/strong&gt; When a follower sends a &lt;code&gt;FetchRequest&lt;/code&gt;, it does two things:

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Asks for new data:&lt;/strong&gt; "Give me everything starting from Offset 101."&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Confirms old data:&lt;/strong&gt; By asking for 101, it implicitly tells the Leader, &lt;strong&gt;"I have successfully written everything up to Offset 100."&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ISR List:&lt;/strong&gt; The Leader uses this fetch request to update its &lt;strong&gt;In-Sync Replica (ISR)&lt;/strong&gt; list. Once all replicas in the ISR list have fetched the message, the Leader advances the "High Watermark" and sends the ACK to the producer (if &lt;code&gt;acks=all&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Kafka Scalability: Pull vs. Push
&lt;/h2&gt;

&lt;p&gt;Scalability was the major driving factor in the design of Kafka. Because it is highly scalable, you can easily add a large number of consumers without affecting performance or requiring downtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  High Throughput and Producer Performance
&lt;/h3&gt;

&lt;p&gt;Kafka can handle a massive influx of data, often processing &lt;strong&gt;100k+ events per second&lt;/strong&gt; from producers. &lt;/p&gt;

&lt;h3&gt;
  
  
  Consumer Flexibility
&lt;/h3&gt;

&lt;p&gt;Because Kafka consumers &lt;strong&gt;pull&lt;/strong&gt; data from a topic, the system offers several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Variable Pacing:&lt;/strong&gt; Different consumers can process messages at their own pace without affecting the broker or other consumers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diverse Consumption Models:&lt;/strong&gt; Kafka supports multiple workflows simultaneously. For example, one consumer can process messages in &lt;strong&gt;real-time&lt;/strong&gt; for immediate analytics, while another processes the same data in &lt;strong&gt;batch mode&lt;/strong&gt; for long-term storage or reporting.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Comparison: Pull vs. Push at Scale
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Push Model&lt;/th&gt;
&lt;th&gt;Pull Model (Kafka)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consumer Addition&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Broker must manage a new connection and push state for every user.&lt;/td&gt;
&lt;td&gt;Consumers simply point to an offset; minimal overhead for the broker.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Processing Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Broker might overwhelm a slow consumer.&lt;/td&gt;
&lt;td&gt;Consumer requests data only when it has the resources to process it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;System Stability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High risk of "backpressure" issues if consumers lag.&lt;/td&gt;
&lt;td&gt;Inherently stable as consumers manage their own flow control.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>architecture</category>
      <category>dataengineering</category>
      <category>distributedsystems</category>
      <category>interview</category>
    </item>
    <item>
      <title>Learn Neural Networks: Build an XOR Gate From Scratch with Python Step by Step Walkthrough</title>
      <dc:creator>Hayden Cordeiro</dc:creator>
      <pubDate>Mon, 02 Jun 2025 00:01:21 +0000</pubDate>
      <link>https://forem.com/haydencordeiro/learn-neural-networks-build-an-xor-gate-from-scratch-with-python-step-by-step-walkthrough-3gfm</link>
      <guid>https://forem.com/haydencordeiro/learn-neural-networks-build-an-xor-gate-from-scratch-with-python-step-by-step-walkthrough-3gfm</guid>
      <description>&lt;p&gt;For the purpose of this blog we will be building a neural network from scratch using python. &lt;br&gt;
The goal will be for the neural network to learn the XOR gate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1reku0v4owf67lwo62ha.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1reku0v4owf67lwo62ha.png" alt="Image description" width="500" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 1: Create the neural network
&lt;/h1&gt;

&lt;p&gt;The neural network will have 3 layers, input, output and a hidden layer.&lt;br&gt;
The image below showcases the neural network&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F344t3bfvg7ej4ooqkxnk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F344t3bfvg7ej4ooqkxnk.png" alt="Image description" width="800" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 2: Randomly Assign Weights and biases to the network
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3k0x8d02zkn0p50c00tk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3k0x8d02zkn0p50c00tk.png" alt="Image description" width="800" height="648"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 3: Forward Pass
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Some background information you need to know before forward pass.
&lt;/h2&gt;

&lt;p&gt;1) Activation functions (We will be using sigmoid)&lt;br&gt;
2) Linear algebra (Honestly if you do not know this maybe you should stop reading?!)&lt;/p&gt;

&lt;h2&gt;
  
  
  Sigmoid Activation Function:
&lt;/h2&gt;

&lt;p&gt;you do not have to understand the formula for now, just understand that this is the formula and the deravative&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fswd7dr4nso4yoo810zsg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fswd7dr4nso4yoo810zsg.png" alt="Image description" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During the forward pass, the outputs of each neuron are calculated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forward Pass Algorithm
&lt;/h2&gt;

&lt;p&gt;The algorithm is straight forward&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Take the weighted sum of the incoming inputs  * and the weights&lt;/li&gt;
&lt;li&gt;Add the bias of the current neuron to the weighted sum&lt;/li&gt;
&lt;li&gt;Apply the activation function
And viola! you have the output of one neuron.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Let's do it for one neuron together
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs79dj4l5ske56wf6yljy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs79dj4l5ske56wf6yljy.png" alt="Image description" width="800" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;1) Weighted Sum = 0.46 * 1 + 0.03 * 1 = 0.49&lt;br&gt;
2) Adding the bias to the weighted sum = 0.49 + 0.12 = 0.61&lt;br&gt;
3) Applying the activation function (sigmoid) = sigmoid(0.61) = 0.6479 =~ 0.65&lt;br&gt;
(I simply substituted x with 0.61 in (1/1 + e^-x))&lt;/p&gt;

&lt;p&gt;Now you just need to this 7B times more if you want to train a large network, or in case 4 times more. &lt;/p&gt;

&lt;h3&gt;
  
  
  Completed Forward Pass
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8mcjyfdh2iycl64am5m4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8mcjyfdh2iycl64am5m4.png" alt="Image description" width="800" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 4: The Dreaded Backpropagation
&lt;/h1&gt;

&lt;p&gt;There are 2 part of backpropagation&lt;br&gt;
1) Calculate the error and associate how much each neuron contributed to that error&lt;br&gt;
2) Updating Weights to reduce error&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculating error
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbkf7o9dazehsgm508hie.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbkf7o9dazehsgm508hie.png" alt="Image description" width="800" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the last layer (output layer), if you think about it intuitively.&lt;br&gt;
To improve we need two things&lt;br&gt;
1) By how much of a difference are we wrong by&lt;br&gt;
2) Should our outputted value increase or decrease to match the expected output&lt;/p&gt;

&lt;p&gt;Achieving the first part is straightforward, we can calculate&lt;br&gt;
(output- expected output).&lt;/p&gt;

&lt;p&gt;For the second part, you need a little knowledge about calculus (ie derivatives).&lt;/p&gt;

&lt;p&gt;Don't be scared, if you don't know ill help you understand..&lt;/p&gt;

&lt;h4&gt;
  
  
  A derivative can be defined as:
&lt;/h4&gt;

&lt;p&gt;The slope of the tangent to a curve at this point is known as the derivative of the function with respect to x &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5jxnaavar5xswda06eo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5jxnaavar5xswda06eo.png" alt="Image description" width="247" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The goal is to get the error to min (or in this case slope ).&lt;br&gt;
If the slope is positive we must reduce our value and vice versa for a negative slope.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error Calculation Final Layer
&lt;/h3&gt;

&lt;p&gt;= (output- expected_output) * derivative(output)&lt;/p&gt;

&lt;p&gt;The derivative of the output value can be easily found by putting the value of output in place of x in this formula&lt;br&gt;
(output) * (1 - output) (See figure 2 -&amp;gt; Sigmoid and its derivative)&lt;/p&gt;

&lt;h3&gt;
  
  
  Lets solve it together for the first neuron in the last layer
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyk78pvqlmi9n7mqo7v4u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyk78pvqlmi9n7mqo7v4u.png" alt="Image description" width="800" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;= (0.77 - 1) * derivative(0.77)&lt;br&gt;
= -0.23 * derivative(0.77)&lt;br&gt;
= -0.23 * [(0.77) * (1-0.77)]&lt;br&gt;
= - 0.040733&lt;br&gt;
=~ -0.04&lt;/p&gt;

&lt;h4&gt;
  
  
  After doing it for both the neurons in the output layer the network will look something like this
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdmqonx3rhmk80ixyyug8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdmqonx3rhmk80ixyyug8.png" alt="Image description" width="800" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  For neurons in the hidden layers
&lt;/h3&gt;

&lt;p&gt;For the final layers its straightforward, you know the expected output and the output you got.&lt;br&gt;
For the hidden layers you have to determine how much the neuron contributed to the error of the next layer.&lt;br&gt;
Key word : How much?&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzr1skkr87q79dknerxpk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzr1skkr87q79dknerxpk.png" alt="Image description" width="800" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;every neuron might play a part in the error, you want to change the neuron's weight proportional to the impact it has on the next layer.&lt;/p&gt;

&lt;p&gt;Therefore the error can be calculated by&lt;/p&gt;

&lt;p&gt;weighted sum of the product of&lt;br&gt;
(the outgoing weights of from the neuron * error of the corresponding next layer neuron) * ofc lets not forget the derivative &lt;/p&gt;

&lt;p&gt;Lets take the neuron with the output 0.65 (First neuron from top in the hidden layer).&lt;/p&gt;

&lt;p&gt;(I had to bump up the accuracy, since the error deltas are too small)&lt;br&gt;
= (0.37 * -0.040) + (0.13 * 0.146)&lt;br&gt;
= -0.0148+ 0.01898&lt;br&gt;
= 0.00418&lt;/p&gt;

&lt;p&gt;To get error for this neuron&lt;br&gt;
= weighted sum * derivative(output)&lt;br&gt;
= 0.00418* [(0.65) * (1-0.65)]&lt;br&gt;
= 0.00095 &lt;br&gt;
=~ 0.001&lt;/p&gt;

&lt;h3&gt;
  
  
  Completed backpropagation
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftdfz0dm5vve85u6zhi90.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftdfz0dm5vve85u6zhi90.png" alt="Image description" width="800" height="588"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 10000 Maybe? : Finally the last step updating the weights
&lt;/h2&gt;

&lt;p&gt;Gradient descent is a topic I consider out of the scope of the blog, but to give you a quick summary.&lt;/p&gt;

&lt;p&gt;You dont want to update weights too quickly, making large changes to ur weights will sway your networks output by a large value preventing you from getting a model with high accurarcy.&lt;br&gt;
The parameter that controls how much a weight is tweaked is called the learning rate.&lt;/p&gt;

&lt;p&gt;However, to small of a learning rate will lead to spending hours on training , it's a tradeoff that you have to play around with.&lt;/p&gt;

&lt;p&gt;lets assume learning rate of 0.1.&lt;br&gt;
learning_rate = 0.1&lt;/p&gt;

&lt;h4&gt;
  
  
  Tuning weights and biases
&lt;/h4&gt;

&lt;p&gt;weight = weight - (error * learning_rate * input)&lt;br&gt;
bias = bias - (error * learning_rate)&lt;/p&gt;

&lt;h5&gt;
  
  
  updating weight 1 and bias for the first neuron in hidden layer
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2dnuxgxvsg4zz83sf21n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2dnuxgxvsg4zz83sf21n.png" alt="Image description" width="800" height="588"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;weight = 0.46 - (0.001 * 0.1 * 1)&lt;br&gt;
weight = 0.4599 =~ 0.46&lt;/p&gt;

&lt;p&gt;bias = 0.12 - (0.001 * 0.1)&lt;br&gt;
bias = 0.1199 =~ 0.12&lt;/p&gt;

&lt;p&gt;(note: since we are rounding off numbers, it looks like the weight is not updating, but every micro adjustments can make a large change to the network)&lt;/p&gt;

&lt;h3&gt;
  
  
  Weights and biases updated.
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49kby7vya6wqg8ac7h7n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49kby7vya6wqg8ac7h7n.png" alt="Image description" width="800" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although we had rounded off the values some weights and biases did change (marked as green).&lt;/p&gt;

&lt;h3&gt;
  
  
  PHEW!! We are finally done with one pass
&lt;/h3&gt;

&lt;p&gt;The same process takes places 100s of times depending on the number of epochs, batch size and input data set&lt;br&gt;
But lucky for us, we have computers to do that job for us.&lt;/p&gt;

&lt;p&gt;Here is my implementation of a neural network from scratch.&lt;br&gt;
&lt;a href="https://github.com/haydencordeiro/NNFromScratch" rel="noopener noreferrer"&gt;https://github.com/haydencordeiro/NNFromScratch&lt;/a&gt;&lt;br&gt;
Although it might not be a 100% replica, it’s good enough.&lt;/p&gt;

&lt;p&gt;Here is a video of the training of this network (the same example)&lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=PkKaKe0_xCA" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=PkKaKe0_xCA&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congrats you now understand backpropagation (Hopefully!)&lt;/p&gt;

&lt;h3&gt;
  
  
  Disclaimer:
&lt;/h3&gt;

&lt;p&gt;The purpose of this blog is to help you understand how a neural network learns, not to build a production-ready model. For simplicity, we use Mean Squared Error (MSE) as the loss function.&lt;/p&gt;

&lt;p&gt;While MSE is mathematically easier to work with and helps explain the backpropagation process, it isn't ideal for classification problems like XOR. In real-world scenarios, especially for binary classification, Cross-Entropy Loss is generally preferred because it’s better at guiding the model when outputs are close to 0 or 1.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Ensuring Data Consistency Across Microservices: Herding Cats with Saga &amp; Transactional Outbox</title>
      <dc:creator>Hayden Cordeiro</dc:creator>
      <pubDate>Wed, 28 May 2025 02:04:54 +0000</pubDate>
      <link>https://forem.com/haydencordeiro/ensuring-data-consistency-across-microservices-herding-cats-with-saga-outbox-3mhe</link>
      <guid>https://forem.com/haydencordeiro/ensuring-data-consistency-across-microservices-herding-cats-with-saga-outbox-3mhe</guid>
      <description>&lt;p&gt;If you're diving into the world of microservices, you've likely heard of scalability, independent deployments, and tech-stack freedom. &lt;br&gt;
That flexibility is great, but keeping their data in sync can feel like herding cats. This article hands you the super‑power to keep every “cat” in line (ie ensure &lt;strong&gt;Data Consistency&lt;/strong&gt;.)&lt;/p&gt;

&lt;p&gt;Remember the good old days of monoliths? You had one big, cozy database. You wrapped everything in a beautiful, all-or-nothing &lt;strong&gt;ACID&lt;/strong&gt; transaction (Atomicity, Consistency, Isolation, Durability), and life was... well, simpler in &lt;em&gt;that&lt;/em&gt; aspect.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsiedynd7vcr2w8gsb93e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsiedynd7vcr2w8gsb93e.png" alt="Image description" width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But in Microservice-Land, each service proudly has its own database. Our &lt;strong&gt;Order Service&lt;/strong&gt; has its database, and our &lt;strong&gt;Inventory Service&lt;/strong&gt; has its own. They might even be different &lt;em&gt;types&lt;/em&gt; of databases (SQL! NoSQL! Ooh, fancy!). This is great for loose coupling, but how do you make sure that when an order is placed, inventory &lt;em&gt;actually&lt;/em&gt; gets reduced, and what if one of them fails? You can't just stretch an ACID transaction across the network and different databases;  that way lies madness (and often, something called &lt;strong&gt;Two-Phase Commit (2PC)&lt;/strong&gt;, which comes with its own box of horrors like blocking locks and lower availability ).&lt;/p&gt;

&lt;p&gt;So, how do we keep our data from becoming a chaotic mess without tying our services back together? Enter our heroes: The &lt;strong&gt;Saga Pattern&lt;/strong&gt; and the &lt;strong&gt;Transactional Outbox&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Saga Pattern: Your Business Workflow's Storyteller&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think of a &lt;strong&gt;Saga&lt;/strong&gt; as a story i.e. a sequence of events. In our world, it's a &lt;strong&gt;sequence of local transactions&lt;/strong&gt;. Each microservice performs its own piece of the work in its own local transaction and then signals the next service to pick up the task.&lt;/p&gt;

&lt;p&gt;Let's say a customer orders 'Product 1'. The Order Service starts the Saga:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Order Service:&lt;/strong&gt; Creates an order, marks it 'PENDING' (Local Transaction 1). Publishes an 'OrderPlaced' event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inventory Service:&lt;/strong&gt; Hears 'OrderPlaced', reserves inventory (Local Transaction 2). Publishes 'InventoryReserved'.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment Service:&lt;/strong&gt; Hears 'InventoryReserved', charges the card (Local Transaction 3). Publishes 'PaymentProcessed'.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order Service:&lt;/strong&gt; Hears 'PaymentProcessed', updates order to 'CONFIRMED' (Local Transaction 4).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;"But Hayden," you ask, "what if the Payment Service fails? We've already reserved inventory!"&lt;/p&gt;

&lt;p&gt;Aha! That's where the magic of Sagas lies: &lt;strong&gt;Compensating Transactions&lt;/strong&gt;. If any step fails, the Saga triggers 'undo' operations for all the preceding steps that succeeded. So, if payment fails, the Saga would tell the Inventory Service to 'ReleaseInventory' and the Order Service to 'CancelOrder'. Neat, huh? We aim for &lt;strong&gt;eventual consistency&lt;/strong&gt; (the system &lt;em&gt;will&lt;/em&gt; become consistent, just maybe not instantaneously).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choreography vs. Orchestration: Who's Calling the Shots?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sagas come in two main flavors:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Choreography (The Dance Floor):&lt;/strong&gt; Services talk to each other by publishing and listening to events. The Order Service shouts "Order Placed!", Inventory hears it and shouts "Inventory Reserved!", Payment hears &lt;em&gt;that&lt;/em&gt; and shouts "Paid!".

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pros:&lt;/strong&gt; Simple for small workflows, no central bottleneck.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cons:&lt;/strong&gt; Can get messy fast, hard to track, risk of circular 
matches.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fanmny4b06k5bitktc21d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fanmny4b06k5bitktc21d.png" alt="Image description" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration (The Conductor):&lt;/strong&gt; A central &lt;strong&gt;Orchestrator&lt;/strong&gt; acts like a conductor, telling each service what to do and when. "Order Service, create order! Inventory, reserve! Payment, charge!".

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pros:&lt;/strong&gt; Easier for complex flows, avoids cycles, better visibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cons:&lt;/strong&gt; Introduces a central component (potential bottleneck/failure point).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1tgmy8r07pj264n2x2e5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1tgmy8r07pj264n2x2e5.png" alt="Image description" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Know Your Steps: Compensable, Pivot, Retryable&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To build a good Saga, you need to know your transaction types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compensable:&lt;/strong&gt; These can be undone. Think 'Reserve Inventory', you can always 'Release Inventory'.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pivot:&lt;/strong&gt; The &lt;strong&gt;point of no return&lt;/strong&gt;. Once this step succeeds, the Saga &lt;em&gt;must&lt;/em&gt; finish; no going back. Often, this is something irreversible, like charging a credit card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retryable:&lt;/strong&gt; These come &lt;em&gt;after&lt;/em&gt; the pivot. Since we can't go back, these steps &lt;em&gt;must&lt;/em&gt; succeed eventually, even if we have to retry them a bunch of times (making them &lt;strong&gt;idempotent&lt;/strong&gt; is key!). Think 'Send Confirmation Email'.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding these helps you design robust Sagas that know when to roll back and when to push forward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transactional Outbox: The Unshakeable Mailman&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2dv2qljn75kunnk5hdo9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2dv2qljn75kunnk5hdo9.png" alt="Image description" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Okay, Sagas are cool. They coordinate the &lt;em&gt;workflow&lt;/em&gt;. But how does the Order Service reliably tell the Inventory Service that an order was placed? What if it updates its &lt;em&gt;own&lt;/em&gt; database but then crashes &lt;em&gt;before&lt;/em&gt; sending the message to Kafka (or RabbitMQ, or whatever you're using)?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boom!&lt;/strong&gt; Inconsistency. The Order Service thinks an order exists, but nobody else knows. This is the dreaded &lt;strong&gt;dual-write problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;"But Kafka is fault-tolerant!" you cry. Yes, it is... &lt;em&gt;once the message gets there&lt;/em&gt;. The gap between your local DB commit and the message hitting the broker is the danger zone.&lt;/p&gt;

&lt;p&gt;This is where the &lt;strong&gt;Transactional Outbox&lt;/strong&gt; pattern saves the day. It's surprisingly simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When the Order Service creates an order, it does &lt;strong&gt;two things&lt;/strong&gt; in the &lt;strong&gt;same, single, local ACID transaction&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Inserts the 'Order' row into its Orders table.&lt;/li&gt;
&lt;li&gt;Inserts an 'OrderPlaced' event message into a special Outbox table.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Because it's one transaction, either &lt;em&gt;both&lt;/em&gt; writes succeed, or &lt;em&gt;neither&lt;/em&gt; does. No more dual-write gap!&lt;/li&gt;
&lt;li&gt;A separate, reliable &lt;strong&gt;Message Relay&lt;/strong&gt; process monitors the Outbox table.&lt;/li&gt;
&lt;li&gt;This relay picks up new events and &lt;em&gt;reliably&lt;/em&gt; publishes them to your message broker.&lt;/li&gt;
&lt;li&gt;Once &lt;em&gt;successfully&lt;/em&gt; published, the relay marks the event as 'sent' or deletes it from the Outbox.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The relay can use &lt;strong&gt;Polling&lt;/strong&gt; (periodically checking the table ) or &lt;strong&gt;Change Data Capture (CDC)&lt;/strong&gt; (tailing the database logs, often preferred for lower latency and load ).&lt;/p&gt;

&lt;p&gt;The Outbox acts like a durable, guaranteed to send mailbox &lt;em&gt;inside&lt;/em&gt; your service's database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better Together: Saga + Outbox&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So, is it Saga &lt;em&gt;or&lt;/em&gt; Outbox? Nope! It's usually Saga &lt;em&gt;and&lt;/em&gt; Outbox.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Saga&lt;/strong&gt; defines the &lt;em&gt;business logic&lt;/em&gt; and &lt;em&gt;workflow&lt;/em&gt; across services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outbox&lt;/strong&gt; provides the &lt;em&gt;reliable messaging infrastructure&lt;/em&gt; that allows each step in the Saga to communicate without losing messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of Outbox as the &lt;strong&gt;durable pipe&lt;/strong&gt; and Saga as the &lt;strong&gt;traffic controller&lt;/strong&gt;. Every time a Saga step (whether choreographed or orchestrated) needs to send a message, it uses its local Outbox to ensure that message &lt;em&gt;gets out&lt;/em&gt; if, and only if, its local work was successfully committed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When Do I Need This Stuff?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Should &lt;em&gt;every&lt;/em&gt; microservice use Sagas and Outboxes? Probably not! Always aim for simplicity.&lt;/p&gt;

&lt;p&gt;Here’s a quick-and-dirty decision tree:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0omyp9lgortzegy5iuzd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0omyp9lgortzegy5iuzd.png" alt="Image description" width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Does your action touch &amp;gt; 1 service's DB?&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No:&lt;/strong&gt; Hallelujah! Use a simple local ACID transaction. You're done!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yes:&lt;/strong&gt; Keep going...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do you need &lt;em&gt;instantaneous&lt;/em&gt;, strict global consistency?&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Yes:&lt;/strong&gt; Oof. Maybe microservices aren't the best fit here? Consider consolidating or facing the 2PC giant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No (Eventual is OK):&lt;/strong&gt; Keep going...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do you need &lt;em&gt;guaranteed&lt;/em&gt; messaging (no lost events)?&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Yes:&lt;/strong&gt; You need the &lt;strong&gt;Transactional Outbox&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No:&lt;/strong&gt; Maybe simple fire-and-forget messaging is enough (but be careful!).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is there a multi-step flow that might fail midway and needs coordination/rollback?&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Yes:&lt;/strong&gt; You need a &lt;strong&gt;Saga&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No:&lt;/strong&gt; Outbox alone might be sufficient.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;In All Seriousness: Practices &amp;amp; Recommendations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Saga Pattern&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ensure idempotent steps&lt;/strong&gt; – each service can safely retry without side-effects.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clearly label transaction types&lt;/strong&gt; – identify &lt;em&gt;Compensable&lt;/em&gt;, &lt;em&gt;Pivot&lt;/em&gt;, and &lt;em&gt;Retryable&lt;/em&gt; steps to steer the saga correctly.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency &amp;amp; isolation&lt;/strong&gt; – sagas don’t give strict isolation like ACID; design for eventual consistency.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestrator fault tolerance&lt;/strong&gt; – persist saga state in durable storage so a new instance can resume mid-flow.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry &amp;amp; recovery policies&lt;/strong&gt; – set sensible back-off and timeout values to avoid deadlocks or message storms.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loose coupling &amp;amp; contracts&lt;/strong&gt; – publish well-defined event schemas and keep services independently deployable.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Transactional Outbox&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatic outbox writes&lt;/strong&gt; – record the event in the &lt;em&gt;same&lt;/em&gt; database transaction as the business change.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency &amp;amp; retries&lt;/strong&gt; – embed de-duplication keys; consumers must handle duplicates gracefully.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Append-only log schema&lt;/strong&gt; – treat the outbox table as an immutable log; avoid in-place updates.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preserve event ordering&lt;/strong&gt; – maintain ordering &lt;em&gt;per aggregate&lt;/em&gt; when relaying events.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mark items as processed&lt;/strong&gt; – flag or move rows once the broker confirms delivery.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleanup policy&lt;/strong&gt; – archive or delete processed rows regularly to keep the table small.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Takeaway&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Distributed systems are hard, and maintaining data consistency across microservices is one of the trickier puzzles. But by understanding the &lt;strong&gt;Saga pattern&lt;/strong&gt; (for workflow orchestration) and the &lt;strong&gt;Transactional Outbox pattern&lt;/strong&gt; (for reliable messaging), you have powerful tools to build resilient, scalable, and &lt;em&gt;eventually&lt;/em&gt; consistent systems without falling back into the monolithic abyss or getting burned by 2PC.&lt;/p&gt;

&lt;p&gt;Now go forth and build amazing things, may the force be with you!&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>microservices</category>
      <category>eventdriven</category>
    </item>
    <item>
      <title>Database Consistency in Microservices!</title>
      <dc:creator>Hayden Cordeiro</dc:creator>
      <pubDate>Wed, 28 May 2025 01:55:50 +0000</pubDate>
      <link>https://forem.com/haydencordeiro/database-consistency-in-microservices-3859</link>
      <guid>https://forem.com/haydencordeiro/database-consistency-in-microservices-3859</guid>
      <description>&lt;p&gt;If you're diving into the world of microservices, you've likely heard the of scalability, independent deployments, and tech-stack freedom. But beneath these shimmering waters is a beast, one that no man can tame (PS: this article will unlock a superpower which you can use to tame it), known as &lt;strong&gt;Data Consistency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Remember the good old days of monoliths? You had one big, cozy database. You wrapped everything in a beautiful, all-or-nothing &lt;strong&gt;ACID&lt;/strong&gt; transaction (Atomicity, Consistency, Isolation, Durability), and life was... well, simpler in &lt;em&gt;that&lt;/em&gt; respect.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsiedynd7vcr2w8gsb93e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsiedynd7vcr2w8gsb93e.png" alt="Image description" width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But in Microservice-Land, each service proudly has its own database. Our &lt;strong&gt;Order Service&lt;/strong&gt; has its database, and our &lt;strong&gt;Inventory Service&lt;/strong&gt; has its own. They might even be different &lt;em&gt;types&lt;/em&gt; of databases (SQL! NoSQL! Ooh, fancy!). This is great for loose coupling, but how do you make sure that when an order is placed, inventory &lt;em&gt;actually&lt;/em&gt; gets reduced, and what if one of them fails? You can't just stretch an ACID transaction across the network and different databases – that way lies madness (and often, something called &lt;strong&gt;Two-Phase Commit (2PC)&lt;/strong&gt;, which comes with its own box of horrors like blocking locks and lower availability ).&lt;/p&gt;

&lt;p&gt;So, how do we keep our data from becoming a chaotic mess without tying our services back together? Enter our heroes: The &lt;strong&gt;Saga Pattern&lt;/strong&gt; and the &lt;strong&gt;Transactional Outbox&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Saga Pattern: Your Business Workflow's Storyteller&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think of a &lt;strong&gt;Saga&lt;/strong&gt; as a story – a sequence of events. In our world, it's a &lt;strong&gt;sequence of local transactions&lt;/strong&gt;. Each microservice performs its own piece of the work in its own local transaction and then signals the next service to pick up the baton.&lt;/p&gt;

&lt;p&gt;Let's say a customer orders 'Product 1'. The Order Service starts the Saga:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Order Service:&lt;/strong&gt; Creates an order, marks it 'PENDING' (Local Transaction 1). Publishes an 'OrderPlaced' event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inventory Service:&lt;/strong&gt; Hears 'OrderPlaced', reserves inventory (Local Transaction 2). Publishes 'InventoryReserved'.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment Service:&lt;/strong&gt; Hears 'InventoryReserved', charges the card (Local Transaction 3). Publishes 'PaymentProcessed'.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order Service:&lt;/strong&gt; Hears 'PaymentProcessed', updates order to 'CONFIRMED' (Local Transaction 4).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;"But Hayden," you ask, "what if the Payment Service fails? We've already reserved inventory!"&lt;/p&gt;

&lt;p&gt;Aha! That's where the magic of Sagas lies: &lt;strong&gt;Compensating Transactions&lt;/strong&gt;. If any step fails, the Saga triggers 'undo' operations for all the preceding steps that succeeded. So, if payment fails, the Saga would tell the Inventory Service to 'ReleaseInventory' and the Order Service to 'CancelOrder'. Neat, huh? We aim for &lt;strong&gt;eventual consistency&lt;/strong&gt; – the system &lt;em&gt;will&lt;/em&gt; become consistent, just maybe not instantaneously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choreography vs. Orchestration: Who's Calling the Shots?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sagas come in two main flavors:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Choreography (The Dance Floor):&lt;/strong&gt; Services talk to each other by publishing and listening to events. The Order Service shouts "Order Placed!", Inventory hears it and shouts "Inventory Reserved!", Payment hears &lt;em&gt;that&lt;/em&gt; and shouts "Paid!".

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pros:&lt;/strong&gt; Simple for small workflows, no central bottleneck.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cons:&lt;/strong&gt; Can get messy fast, hard to track, risk of circular 
matches.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fanmny4b06k5bitktc21d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fanmny4b06k5bitktc21d.png" alt="Image description" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration (The Conductor):&lt;/strong&gt; A central &lt;strong&gt;Orchestrator&lt;/strong&gt; acts like a conductor, telling each service what to do and when. "Order Service, create order! Inventory, reserve! Payment, charge!".

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pros:&lt;/strong&gt; Easier for complex flows, avoids cycles, better visibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cons:&lt;/strong&gt; Introduces a central component (potential bottleneck/failure point).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1tgmy8r07pj264n2x2e5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1tgmy8r07pj264n2x2e5.png" alt="Image description" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Know Your Steps: Compensable, Pivot, Retryable&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To build a good Saga, you need to know your transaction types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compensable:&lt;/strong&gt; These can be undone. Think 'Reserve Inventory' – you can always 'Release Inventory'.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pivot:&lt;/strong&gt; The &lt;strong&gt;point of no return&lt;/strong&gt;. Once this step succeeds, the Saga &lt;em&gt;must&lt;/em&gt; finish; no going back. Often, this is something irreversible, like charging a credit card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retryable:&lt;/strong&gt; These come &lt;em&gt;after&lt;/em&gt; the pivot. Since we can't go back, these steps &lt;em&gt;must&lt;/em&gt; succeed eventually, even if we have to retry them a bunch of times (making them &lt;strong&gt;idempotent&lt;/strong&gt; is key!). Think 'Send Confirmation Email'.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding these helps you design robust Sagas that know when to roll back and when to push forward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transactional Outbox: The Unshakeable Mailman&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2dv2qljn75kunnk5hdo9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2dv2qljn75kunnk5hdo9.png" alt="Image description" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Okay, Sagas are cool. They coordinate the &lt;em&gt;workflow&lt;/em&gt;. But how does the Order Service reliably tell the Inventory Service that an order was placed? What if it updates its &lt;em&gt;own&lt;/em&gt; database but then crashes &lt;em&gt;before&lt;/em&gt; sending the message to Kafka (or RabbitMQ, or whatever you're using)?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boom!&lt;/strong&gt; Inconsistency. The Order Service thinks an order exists, but nobody else knows. This is the dreaded &lt;strong&gt;dual-write problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;"But Kafka is fault-tolerant!" you cry. Yes, it is... &lt;em&gt;once the message gets there&lt;/em&gt;. The gap between your local DB commit and the message hitting the broker is the danger zone.&lt;/p&gt;

&lt;p&gt;This is where the &lt;strong&gt;Transactional Outbox&lt;/strong&gt; pattern saves the day. It's surprisingly simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When the Order Service creates an order, it does &lt;strong&gt;two things&lt;/strong&gt; in the &lt;strong&gt;same, single, local ACID transaction&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Inserts the 'Order' row into its Orders table.&lt;/li&gt;
&lt;li&gt;Inserts an 'OrderPlaced' event message into a special Outbox table.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Because it's one transaction, either &lt;em&gt;both&lt;/em&gt; writes succeed, or &lt;em&gt;neither&lt;/em&gt; does. No more dual-write gap!&lt;/li&gt;
&lt;li&gt;A separate, reliable &lt;strong&gt;Message Relay&lt;/strong&gt; process monitors the Outbox table.&lt;/li&gt;
&lt;li&gt;This relay picks up new events and &lt;em&gt;reliably&lt;/em&gt; publishes them to your message broker.&lt;/li&gt;
&lt;li&gt;Once &lt;em&gt;successfully&lt;/em&gt; published, the relay marks the event as 'sent' or deletes it from the Outbox.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The relay can use &lt;strong&gt;Polling&lt;/strong&gt; (periodically checking the table ) or &lt;strong&gt;Change Data Capture (CDC)&lt;/strong&gt; (tailing the database logs, often preferred for lower latency and load ).&lt;/p&gt;

&lt;p&gt;The Outbox acts like a durable, guaranteed-to-send mailbox &lt;em&gt;inside&lt;/em&gt; your service's database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better Together: Saga + Outbox&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So, is it Saga &lt;em&gt;or&lt;/em&gt; Outbox? Nope! It's usually Saga &lt;em&gt;and&lt;/em&gt; Outbox.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Saga&lt;/strong&gt; defines the &lt;em&gt;business logic&lt;/em&gt; and &lt;em&gt;workflow&lt;/em&gt; across services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outbox&lt;/strong&gt; provides the &lt;em&gt;reliable messaging infrastructure&lt;/em&gt; that allows each step in the Saga to communicate without losing messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of Outbox as the &lt;strong&gt;durable pipe&lt;/strong&gt; and Saga as the &lt;strong&gt;traffic controller&lt;/strong&gt;. Every time a Saga step (whether choreographed or orchestrated) needs to send a message, it uses its local Outbox to ensure that message &lt;em&gt;gets out&lt;/em&gt; if, and only if, its local work was successfully committed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When Do I Need This Stuff?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Should &lt;em&gt;every&lt;/em&gt; microservice use Sagas and Outboxes? Probably not! Always aim for simplicity.&lt;/p&gt;

&lt;p&gt;Here’s a quick-and-dirty decision tree:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0omyp9lgortzegy5iuzd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0omyp9lgortzegy5iuzd.png" alt="Image description" width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Does your action touch &amp;gt; 1 service's DB?&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No:&lt;/strong&gt; Hallelujah! Use a simple local ACID transaction. You're done!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yes:&lt;/strong&gt; Keep going...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do you need &lt;em&gt;instantaneous&lt;/em&gt;, strict global consistency?&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Yes:&lt;/strong&gt; Oof. Maybe microservices aren't the best fit here? Consider consolidating or facing the 2PC hydra.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No (Eventual is OK):&lt;/strong&gt; Keep going...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do you need &lt;em&gt;guaranteed&lt;/em&gt; messaging (no lost events)?&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Yes:&lt;/strong&gt; You need the &lt;strong&gt;Transactional Outbox&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No:&lt;/strong&gt; Maybe simple fire-and-forget messaging is enough (but be careful!).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is there a multi-step flow that might fail midway and needs coordination/rollback?&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Yes:&lt;/strong&gt; You need a &lt;strong&gt;Saga&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No:&lt;/strong&gt; Outbox alone might be sufficient.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The Takeaway 🎉&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Distributed systems are hard, and maintaining data consistency across microservices is one of the trickier puzzles. But by understanding the &lt;strong&gt;Saga pattern&lt;/strong&gt; (for workflow orchestration) and the &lt;strong&gt;Transactional Outbox pattern&lt;/strong&gt; (for reliable messaging), you have powerful tools to build resilient, scalable, and &lt;em&gt;eventually&lt;/em&gt; consistent systems without falling back into the monolithic abyss or getting burned by 2PC.&lt;/p&gt;

&lt;p&gt;Now go forth and build amazing things!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Dealing with Mountains of Data: Why Just Buying a Bigger Hard Drive Won't Cut It</title>
      <dc:creator>Hayden Cordeiro</dc:creator>
      <pubDate>Fri, 09 May 2025 01:57:39 +0000</pubDate>
      <link>https://forem.com/haydencordeiro/dealing-with-mountains-of-data-why-just-buying-a-bigger-hard-drive-wont-cut-it-26ec</link>
      <guid>https://forem.com/haydencordeiro/dealing-with-mountains-of-data-why-just-buying-a-bigger-hard-drive-wont-cut-it-26ec</guid>
      <description>&lt;p&gt;Large scale applications have large amounts of data. I'm not talking about a few gigabytes or terabytes, rather &lt;em&gt;petabytes&lt;/em&gt; of data. Although 30TB hard drives might be a thing now (Thanks Seagate! [&lt;a href="https://www.techradar.com/pro/seagate-confirms-that-30tb-hard-drives-are-coming-in-early-2024-but-you-probably-wont-be-able-to-use-it-in-your-pc%5D" rel="noopener noreferrer"&gt;https://www.techradar.com/pro/seagate-confirms-that-30tb-hard-drives-are-coming-in-early-2024-but-you-probably-wont-be-able-to-use-it-in-your-pc]&lt;/a&gt;), they simply don't suffice for these truly large-scale applications.&lt;/p&gt;

&lt;p&gt;Let's take Instagram for example:&lt;/p&gt;

&lt;p&gt;They probably have a user table that resembles something like this (If you are a dev from Meta reading this and this is &lt;em&gt;exactly&lt;/em&gt; how your table looks, I'm a Genius! I leaked your user data!!!! &lt;em&gt;Evil laugh&lt;/em&gt;)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;th&gt;user_name&lt;/th&gt;
&lt;th&gt;phone_number&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;user1&lt;/td&gt;
&lt;td&gt;12269765432&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;user2&lt;/td&gt;
&lt;td&gt;12269765432&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;user3&lt;/td&gt;
&lt;td&gt;12269765432&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Coming back to the topic, this user data of 3 rows seems pretty harmless, right? RIGHT! But now imagine 2 billion of these records (Yes, that's how many of you are spending time scrolling, including you reading this article, I see you!). If each row takes around 10KB to store (Just an arbitrary value, don't sue me!), the 3 rows would barely take 30KB. However, for 2 billion, that's a whole other story.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Size per row: 10 KB
Number of rows: 2 billion (2,000,000,000)

Total size = Size per row × Number of rows
Total size = 10 KB/row × 2,000,000,000 rows
Total size = 20,000,000,000 KB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yeah, I'm not reading that number out loud, but I think we both can agree that it's a lot! Much larger than a single hard disk can store!&lt;/p&gt;

&lt;p&gt;Oh, you don't believe me? Fine, take more numbers, that might help:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB
1 PB = 1024 TB

Total size in MB = 20,000,000,000 KB / 1024 ≈ 19,531,250 MB
Total size in GB = 19,531,250 MB / 1024 ≈ 19,073 GB
Total size in TB = 19,073 GB / 1024 ≈ 18.63 TB (Terabytes)
Total size in PB = 18.63 TB / 1024 ≈ 0.018 PB (Petabytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I hope we are both on the same page (both figuratively and literally).&lt;/p&gt;

&lt;p&gt;Okay, but hypothetically, what if some genie or a magician waves a magic wand and we now have a disk that can store such large amounts of data?&lt;/p&gt;

&lt;p&gt;It would still not solve all our problems. We would now have a SPOF (you read that right, not SPF you skincare freaks!) – A SINGLE POINT OF FAILURE. If that one giant disk fails, your entire application is down. Game over.&lt;/p&gt;

&lt;p&gt;At this point, you might be thinking, "Hey Hayden, why are you telling me this? I already have enough problems in my life!"&lt;/p&gt;

&lt;p&gt;I'll let my buddy Dan answer that:&lt;/p&gt;

&lt;p&gt;Dan Martell’s : "A problem well defined is a problem half solved."&lt;/p&gt;

&lt;p&gt;See, he's the best!&lt;/p&gt;

&lt;p&gt;Now, back to our topic! We have two main problems:&lt;br&gt;
1) No single disk large enough to hold the large amount of data.&lt;br&gt;
2) We do not want a single point of failure.&lt;/p&gt;

&lt;p&gt;If you're jumping up right now with the answer, give yourself a pat on the back! Yes, the solution is: why don't we have multiple copies of the database? That would fix the issue with a single point of failure, right? But wait, we can't just &lt;em&gt;duplicate&lt;/em&gt; the &lt;em&gt;entire&lt;/em&gt; dataset, right? Since it doesn't fit on one disk to begin with!&lt;/p&gt;

&lt;p&gt;So, we have to &lt;strong&gt;divide (partition)&lt;/strong&gt; the data across multiple databases or servers.&lt;/p&gt;

&lt;p&gt;And yes, that's our answer: &lt;strong&gt;Partitioning / Sharding&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You've probably heard this before, but I hope that you now understand the crucial use case as to &lt;em&gt;why&lt;/em&gt; it's required for large-scale applications.&lt;/p&gt;

&lt;p&gt;Now, let's come back to the topic of &lt;em&gt;how&lt;/em&gt; we divide this data. We could shard or partition the database using multiple strategies.&lt;/p&gt;

&lt;p&gt;One approach would be partitioning users based on their &lt;strong&gt;region&lt;/strong&gt;. For example, users from the US could be written to a database server located in the US, the same for EU or APAC (Asia Pacific for those of you who do not know).&lt;/p&gt;

&lt;p&gt;The merits of this would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reduced Latency:&lt;/strong&gt; Since the servers are located closer to the users, query time would generally be faster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier Compliance:&lt;/strong&gt; Data can be stored within specific geographical boundaries, helping with data residency regulations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, nothing is always a bed of roses, right? What happens if a user moves from one region to another? Or how about regions where traffic is significantly higher than others? There are a lot of other issues that we could go into with regional partitioning (like complex cross-region queries).&lt;/p&gt;

&lt;p&gt;But coming to the point that got me writing this blog in the first place: &lt;strong&gt;Hashing, Consistent Hashing&lt;/strong&gt; to be more precise.&lt;/p&gt;

&lt;p&gt;Yes, we &lt;em&gt;could&lt;/em&gt; divide and partition the databases according to the user's region, but what if we had something smarter decide which partition a user's record should be written to? It takes the username, user ID, phone number and, &lt;em&gt;voila&lt;/em&gt;, tells you that you belong to partition 2. All hail our AI overlords! Just kidding (riding on the AI hype, lol!), we do not need AI for this. We have something very cool: &lt;strong&gt;Hash Functions!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are a CS major or know a thing or two about crypto, you've probably heard phrases like these associated with hash functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deterministic&lt;/li&gt;
&lt;li&gt;Efficient to Compute&lt;/li&gt;
&lt;li&gt;Uniform Distribution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You know who could &lt;em&gt;really&lt;/em&gt; benefit from these attributes? Yes, Databases! (You are so smart, light pat on your head!)&lt;/p&gt;

&lt;p&gt;Okay, let's say we give the hash function a few attributes from our user table. If you ask why more than one (don't worry, we'll get back to that later).&lt;/p&gt;

&lt;h2&gt;
  
  
  Hashing:
&lt;/h2&gt;

&lt;p&gt;Hash(user_id, user_name, phone_number) -&amp;gt; SomeUniqueOrSemiUniqueHashValue&lt;/p&gt;

&lt;p&gt;Now the question is, how do we take this &lt;code&gt;SomeUniqueOrSemiUniqueHashValue&lt;/code&gt; and map that to a database partition? Do you remember how we might make a circular array? Yes, that's the &lt;strong&gt;modulo (%) operator&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;So, if we had 10 servers (or partitions), we could simply take the &lt;code&gt;SomeUniqueOrSemiUniqueHashValue % 10&lt;/code&gt;, which would give us an output between 0 and 9 (since 10 % 10 = 0).&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;SomeUniqueOrSemiUniqueHashValue % 10&lt;/code&gt; returned 2, we would simply insert the user record in partition 2. Easy peasy!&lt;/p&gt;

&lt;p&gt;Coming back to the point on why we are giving 3 attributes instead of just 1 to the hash function:&lt;/p&gt;

&lt;p&gt;Using a combination of attributes like &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;user_name&lt;/code&gt;, and &lt;code&gt;phone_number&lt;/code&gt; can contribute to a more uniform distribution of hash values. If we only used &lt;code&gt;user_id&lt;/code&gt; and they were assigned sequentially, we might still end up with hot spots as new users are constantly being added to a specific range of IDs. By including other attributes, we increase the entropy and randomness of the hash output, which helps in distributing the data more evenly across the partitions. It's like mixing colors to get a more complex and evenly spread shade.&lt;/p&gt;

&lt;p&gt;Now, let me throw the curveball! Are you ready?&lt;/p&gt;

&lt;p&gt;Let's say our user base grows 10x tomorrow. That would mean we would have to add more database servers, right? And that would also mean that our calculation &lt;code&gt;SomeUniqueOrSemiUniqueHashValue % N&lt;/code&gt;, where N was initially 10, would change to something more like 20.&lt;/p&gt;

&lt;p&gt;So, if &lt;code&gt;SomeUniqueOrSemiUniqueHashValue % 10&lt;/code&gt; returned 2 earlier, &lt;code&gt;SomeUniqueOrSemiUniqueHashValue % 20&lt;/code&gt; might &lt;em&gt;not necessarily&lt;/em&gt; return 2. It could return 3, 4, 5, or any other number. You get my point?&lt;/p&gt;

&lt;p&gt;Let's maybe run through an example:&lt;/p&gt;

&lt;p&gt;When you change &lt;code&gt;N&lt;/code&gt; to a new number (N_new), the result of the modulo operation (&lt;code&gt;hash() % N_new&lt;/code&gt;) will be different for &lt;strong&gt;almost every existing piece of data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine you have 4 partitions (&lt;code&gt;N=4&lt;/code&gt;) and a user's data hashes to 10. &lt;code&gt;10 % 4 = 2&lt;/code&gt;, so their data is in Partition 2.&lt;br&gt;
Now, you add 4 more partitions, so you have 8 (N_new=8). The same user's data with the hash 10 would now be assigned to &lt;code&gt;10 % 8 = 2&lt;/code&gt;. In this specific case, the partition number is the same, but this is purely coincidental.&lt;/p&gt;

&lt;p&gt;Consider another user whose data hashes to 11. With 4 partitions, &lt;code&gt;11 % 4 = 3&lt;/code&gt;, so their data is in Partition 3. With 8 partitions, &lt;code&gt;11 % 8 = 3&lt;/code&gt;. Still the same.&lt;/p&gt;

&lt;p&gt;Okay, let's try a hash of 13. With 4 partitions, &lt;code&gt;13 % 4 = 1&lt;/code&gt;. With 8 partitions, &lt;code&gt;13 % 8 = 5&lt;/code&gt;. This user's data needs to move from Partition 1 to Partition 5.&lt;/p&gt;

&lt;p&gt;How about a hash of 14? With 4 partitions, &lt;code&gt;14 % 4 = 2&lt;/code&gt;. With 8 partitions, &lt;code&gt;14 % 8 = 6&lt;/code&gt;. This user's data needs to move from Partition 2 to Partition 6.&lt;/p&gt;

&lt;p&gt;As you can see, a significant portion of your data would need to be re-calculated and moved to a different partition whenever you change the number of nodes. This process, known as &lt;strong&gt;rehashing&lt;/strong&gt;, is very disruptive, resource-intensive, and can lead to significant downtime for your application while the data is being migrated. Not ideal for a system that needs to be available 24/7!&lt;/p&gt;

&lt;h3&gt;
  
  
  Consistent Hashing (Our Superhero!)
&lt;/h3&gt;

&lt;p&gt;So, you see the problem, right? Simple hashing with that &lt;code&gt;%&lt;/code&gt; operator is like trying to organize a growing party where every time a new guest arrives or someone leaves, you have to ask &lt;em&gt;everyone&lt;/em&gt; to move to a completely new spot. Chaos!&lt;/p&gt;

&lt;p&gt;Consistent Hashing walks in, puts on its cape, and says, "Hold my perfectly distributed data." It tackles this rehashing headache with a fundamentally different approach.&lt;/p&gt;

&lt;p&gt;Instead of thinking about servers numbered 0 to N, imagine a ring. A giant, conceptual ring representing the space of all possible hash values. We take our hash function – that cool, deterministic one that takes your &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;user_name&lt;/code&gt;, &lt;code&gt;phone_number&lt;/code&gt;, and spits out a value – and we use that same function to map &lt;strong&gt;both our users (data)&lt;/strong&gt; and our &lt;strong&gt;database servers (nodes)&lt;/strong&gt; onto this ring.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Funjr3sihwpwxfli7rooy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Funjr3sihwpwxfli7rooy.png" alt="Image description" width="800" height="659"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, your user data hashes to a point on the ring, and each of your database servers also hashes to one or more points on this same ring.&lt;/p&gt;

&lt;p&gt;Now, when we want to store or find a user's data, we take their hash value, find its spot on the ring, and then we move &lt;strong&gt;clockwise&lt;/strong&gt; around the ring until we hit the &lt;em&gt;first&lt;/em&gt; server node. &lt;em&gt;That&lt;/em&gt; server node is where that user's data lives.&lt;/p&gt;

&lt;p&gt;Think of the ring like a timeline or a sorted list of all possible hash values. Each server is responsible for a segment of that ring, from the last server it encountered going counter-clockwise, up to its own position.&lt;/p&gt;

&lt;p&gt;Okay, superhero time! What happens when we need to add a new database server? We hash our new server, and place it onto the ring at its hash position. This new server now takes responsibility for a section of the ring that was previously handled by the server immediately clockwise to it.&lt;/p&gt;

&lt;p&gt;And here's the magic: &lt;strong&gt;Only the data keys that fall into that specific segment of the ring that the new server just claimed need to move!&lt;/strong&gt; All the other users, whose hash values land in segments of the ring &lt;em&gt;not&lt;/em&gt; affected by the new server's arrival, stay exactly where they are. No mass migration needed!&lt;/p&gt;

&lt;p&gt;Similarly, if a server has to leave the ring (sad face, maybe it retired), the server immediately clockwise to the departing server on the ring simply takes over the range of hash values that the old server was responsible for. Again, only the data from the &lt;em&gt;leaving&lt;/em&gt; server needs to be moved to its new neighbor.&lt;/p&gt;

&lt;p&gt;This is why consistent hashing is crucial in building massively scalable and resilient distributed systems. It allows us to grow or shrink our database infrastructure without causing an application-wide meltdown. It's the unsung hero keeping your favorite large-scale apps running smoothly as their data (and user base!) explodes.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
