<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Chidinma's Blog]]></title><description><![CDATA[This blog explores how systems behave beyond the happy path, where assumptions are tested, trade-offs become visible, and failures are often inevitable.
Most pi]]></description><link>https://chidinma.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 17 May 2026 03:04:48 GMT</lastBuildDate><atom:link href="https://chidinma.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Constructing Order Without Time]]></title><description><![CDATA[If you’ve made it this far, you’ve accepted a few uncomfortable truths:
Clocks lie. Ordering isn't about time. Causality is what actually matters. And sometimes, even that isn't enough.
So, if we can’]]></description><link>https://chidinma.dev/constructing-order-without-time</link><guid isPermaLink="true">https://chidinma.dev/constructing-order-without-time</guid><category><![CDATA[distributed systems]]></category><category><![CDATA[System Design]]></category><category><![CDATA[causality]]></category><category><![CDATA[Backend Engineering]]></category><category><![CDATA[lamport clocks]]></category><category><![CDATA[vector clocks]]></category><category><![CDATA[concurrency]]></category><category><![CDATA[ordering]]></category><dc:creator><![CDATA[Chidinma]]></dc:creator><pubDate>Sat, 18 Apr 2026 10:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/e43f4ef7-9129-4e81-91bb-8971b1837fde.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you’ve made it this far, you’ve accepted a few uncomfortable truths:</p>
<p><a href="https://chidinma.dev/why-time-breaks-distributed-systems">Clocks lie</a>. Ordering isn't about time. <a href="https://chidinma.dev/ordering-not-time">Causality is what actually matters</a>. And sometimes, even that isn't enough.</p>
<p>So, if we can’t rely on clocks, how do systems keep track of order at all?</p>
<p>They invent their own time.</p>
<p>Not physical time. <strong>Logical time.</strong></p>
<p>We stop looking at the sun and start counting.</p>
<h2>Logical Clocks</h2>
<p>A logical clock doesn’t try to tell you what time it is.</p>
<p>It doesn’t know about seconds. It doesn’t care about time zones. It doesn’t try to match the real world.</p>
<p>A logical clock answers one question: <strong>What is the order of events?</strong></p>
<p>Instead of measuring time, it tracks relationships. Instead of counting seconds, it counts causality. Instead of measuring the universe, it measures influence.</p>
<p>Every process keeps a counter. When something happens locally, it increases. When a message is sent, the current value goes with it. When a message is received, the counter updates to reflect what it just learned.</p>
<p>That’s it.</p>
<p>Logical clocks track how information flows through the system. They are a map of who talked to whom and in what order.</p>
<h3><strong>Why this works</strong></h3>
<p>Remember <a href="https://chidinma.dev/ordering-not-time">happens-before</a>?</p>
<p>If <code>A → B</code>, we want B’s timestamp to reflect that it came after A.</p>
<p>Logical clocks guarantee exactly that.</p>
<p>No GPS. No quartz crystals. No atomic time. Just counters and message exchange.</p>
<p>They don't solve every problem. They won't tell you what time to meet for lunch, and they won't give you a global "now."</p>
<p>But they solve the <em>right</em> problem: detecting order when it exists.</p>
<p>And there isn’t just one kind.</p>
<p>Lamport clocks use a simple counter to preserve causal order. Vector clocks detect concurrency. Hybrid clocks blend physical and logical time.</p>
<p>Each exists because different systems need different trade-offs.</p>
<p>The simplest one, the one that started it all, is just a counter.</p>
<h2>Lamport Clocks: The Simple Counter</h2>
<p>Leslie Lamport's insight was that if we can't trust seconds, we can at least trust <strong>counting</strong>.</p>
<p>Lamport clocks are simple. Each process keeps a single integer counter. That’s it.</p>
<p>With that one number, we can preserve causal ordering across a distributed system.</p>
<p>Lamport clocks follow three simple rules.</p>
<ol>
<li><p><strong>The Local Rule:</strong> Every time a machine does something (writes to a disk, processes a request), it increments its own counter by 1.</p>
</li>
<li><p><strong>The Sending Rule:</strong> When a machine sends a message, it attaches its current counter value to that message.</p>
</li>
<li><p><strong>The Receiving Rule:</strong> When a machine receives a message, it takes the higher of its own counter and the received one, then adds 1. That's just: <code>max(local_counter, received_counter) + 1</code>.</p>
</li>
</ol>
<p>That’s the whole algorithm.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/bab3e62a-cfdd-46f5-bdec-65403ea2e7e9.png" alt="Two processes (A and B) shown as vertical timelines. A message is sent from an event in Process A with logical clock 3 to Process B. On receive, Process B updates its clock from 2 to 4 using max(2,3)+1, illustrating that if A → B, then LC(A) &lt; LC(B)." style="display:block;margin:0 auto" />

<blockquote>
<p>Lamport clock update on message receive: Process B advances its counter to preserve causality after receiving a message from Process A.</p>
</blockquote>
<p>Every event gets a logical timestamp.</p>
<p>Lamport clocks guarantee that if <code>A → B</code>, then <code>timestamp(A) &lt; timestamp(B)</code>. If I am at "Logical Time 5" and I send you a message, whatever you do next <strong>must</strong> be at least "Logical Time 6."</p>
<p>Causality is preserved.</p>
<p>No clocks drifting. No NTP. No leap seconds. Just integers increasing in response to information flow.</p>
<p>And for many distributed systems, that’s enough.</p>
<h3>The catch</h3>
<p>Lamport clocks preserve causality. But they do not capture it completely.</p>
<p>If you see that <code>timestamp(A) &lt; timestamp(B)</code>, that does <strong>not</strong> necessarily mean: <code>A → B</code>.</p>
<p>Because Lamport clocks collapse everything into a single number, and a single number forces a comparison even when one doesn't exist.</p>
<p>Two concurrent events will still receive different timestamps. One smaller, one larger. But that ordering might be arbitrary.</p>
<p>Lamport clocks give us: If <code>A → B</code>, then <code>LC(A) &lt; LC(B)</code></p>
<p>But they do <em>not</em> give us: If <code>LC(A) &lt; LC(B)</code>, then <code>A → B</code></p>
<p>The arrow only goes one way.</p>
<p>Causality implies ordering. Ordering does not imply causality.</p>
<p>Lamport clocks are <strong>lossy</strong>. They tell you what couldn’t have happened. But they can't prove what did.</p>
<p>That asymmetry matters.</p>
<h3><strong>Turning Lamport clocks into total order</strong></h3>
<p>If we take Lamport timestamps and add a tie-breaker. For example, process ID, we can force a total order.</p>
<p>For any two events:</p>
<ul>
<li><p>Compare their timestamps.</p>
</li>
<li><p>If equal, compare process IDs.</p>
</li>
</ul>
<p>Now every pair of events is comparable. We’ve flattened the causal web into a single sequence.</p>
<p>But that total order is artificial. It respects causality where it exists, but it also imposes order where none existed.</p>
<h3><strong>When it’s enough</strong></h3>
<p>Lamport clocks are simple, lightweight, cheap to maintain, and easy to implement.</p>
<p>They’re often enough when:</p>
<ul>
<li><p>You need to preserve causality.</p>
</li>
<li><p>You want to impose a deterministic order.</p>
</li>
<li><p>You <strong>don’t</strong> need to detect concurrency explicitly.</p>
</li>
</ul>
<p>But sometimes you don’t just want to know which event has the smaller number. You want to know whether two events were independent.</p>
<p>Did two users edit the same file without seeing each other’s changes? Did two replicas diverge without knowing it?</p>
<p>A single counter can’t answer that.</p>
<p>Because to detect independence, you need more than order.</p>
<p>You need <em>ancestry</em>.</p>
<h2>Vector Clocks: Tracking the Ancestry</h2>
<p>Lamport clocks gave us order. But they did it by collapsing everything into a single number.</p>
<p>That number preserved causality. But it erased independence.</p>
<p>If two events were concurrent, Lamport would still force one to look “earlier.”</p>
<p>Sometimes that’s fine. But sometimes, you don’t just want order. You want to know: “<strong>Were these two events independent?</strong>”</p>
<p>To answer that, you need more than a single counter.</p>
<p>You need memory.</p>
<p>Instead of one number per process, each process keeps a <strong>vector</strong>: one slot for every process in the system. If there are three processes- A, B, and C, each process maintains: <code>[A_count, B_count, C_count]</code></p>
<p>Each position tells you how many events from that process have been seen. Not just locally. Globally, from their perspective.</p>
<p>The rules follow the same shape as Lamport, but trade simplicity for information.</p>
<ol>
<li><p><strong>The Local Rule:</strong> Increment only your own position in the vector. If Machine A does something, it only increments its own slot in the vector: <code>[A: 1, B: 0, C: 0]</code>.</p>
</li>
<li><p><strong>The Sending Rule:</strong> Attach your entire vector to the message. You're not just sending the time. You're sending your worldview.</p>
</li>
<li><p><strong>The Receiving Rule:</strong> For every position, take the maximum of your current value and the received value. Then increment your own slot. When you receive a message, you merge histories.</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/7681fa18-d9d2-465d-ba4a-a014fa321162.png" alt="Vector clock merge and concurrency example" style="display:block;margin:0 auto" />

<blockquote>
<p>Process B merges A’s history and advances, while Process C evolves independently. Revealing causality and concurrency.</p>
</blockquote>
<p>We can now compare events. That gives us something Lamport couldn't.</p>
<p><strong>Event A happened-before B if every element in A’s vector is ≤ B’s vector, and at least one is strictly less.</strong></p>
<p>If neither vector is less than the other, they are concurrent. Not maybe. Provably independent.</p>
<p><strong>Example:</strong></p>
<pre><code class="language-jsx">A: [2,1]
B: [1,2]
</code></pre>
<p>Each is greater in one dimension. Neither contains the other. That is concurrency.</p>
<p>A vector clock is not a timestamp. It’s a summary of ancestry.</p>
<p>When I receive your vector, I don’t just learn when something happened.</p>
<p>I learn who influenced you.</p>
<p>If my vector is: <code>[A:5, B:2, C:8]</code>, I am saying: I have seen 5 events from A, 2 from B, 8 from C.</p>
<p>It’s a compact map of causal history.</p>
<p>Lamport gave us a line. Vector clocks give us the tree.</p>
<h3><strong>The "Shopping Cart" Problem</strong></h3>
<p>Imagine you are shopping:</p>
<ol>
<li><p>You add a book to your cart on your phone.</p>
</li>
<li><p>Your phone loses signal or goes offline.</p>
</li>
<li><p>You add a shirt on your laptop.</p>
</li>
</ol>
<p>These two updates happened on different machines. They never saw each other.</p>
<p>With Lamport clocks, the system would assign different numbers and pick a “winner” using an arbitrary tie-breaker.</p>
<p>Oops. Your book disappears.</p>
<p>The system “resolved” a conflict you didn't even know you had by deleting data.</p>
<p>But with <strong>Vector Clocks</strong>, the database looks at the ancestry and sees the truth: <strong>these updates are concurrent.</strong></p>
<p>It doesn’t guess. It keeps both. It says: <em>“I have two independent versions of this cart. I’m going to keep both, and the next time the user is online, I’ll let them merge the two.”</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/9cf33ced-6a32-462c-acfc-00d322a52d9e.png" alt="Vector clocks. Divergence and merge" style="display:block;margin:0 auto" />

<blockquote>
<p>Two replicas diverge without communication; on merge, their concurrent updates are preserved rather than forced into an order.</p>
</blockquote>
<p>This is exactly how systems like <strong>Amazon Dynamo</strong> or <strong>Riak</strong> detect conflicts. They don't try to force a "fake" order on independent events. They detect concurrency first. Resolution comes later.</p>
<h3><strong>The Trade-off</strong></h3>
<p>Vector clocks are expressive. But they’re also <strong>expensive</strong>.</p>
<p>They require:</p>
<ul>
<li><p>O(n) metadata per event. The size of the timestamp grows with the number of machines. If you have 1,000 nodes, every single message has to carry 1,000 integers.</p>
</li>
<li><p>Larger messages. The full vector travels with each message.</p>
</li>
<li><p>Knowledge of all participants.</p>
</li>
<li><p>Complexity of membership management when nodes join/leave.</p>
</li>
</ul>
<p>That does not scale casually.</p>
<p>Vector clocks are the “don’t lose data” tool. You pay in metadata to preserve independence.</p>
<h3>When they make sense</h3>
<p>Vector clocks are the right tool when:</p>
<ul>
<li><p>You need to detect concurrent updates.</p>
</li>
<li><p>You want to resolve conflicts intelligently.</p>
</li>
<li><p>You care about causality <em>and</em> independence.</p>
</li>
</ul>
<p>They show up in systems like version control, distributed databases, and conflict-resolution protocols.</p>
<p>But they are heavy. And sometimes,  we still want something close to real-world time.</p>
<p>Just not blindly.</p>
<h2><strong>The Takeaway</strong></h2>
<p>We started this journey by saying: Clocks lie.</p>
<p>They do. But understanding how they lie is what lets us design around them.</p>
<p><a href="https://chidinma.dev/why-time-breaks-distributed-systems">Physical clocks approximate reality</a>. Logical clocks preserve causality. Vector clocks detect independence.</p>
<p>We can now track causality and detect independence without ever looking at a wall clock.</p>
<p>But in doing so, we've lost the real world.</p>
<p>Causality tells you what came before what. It tells you nothing about when.</p>
<p>So what happens when the real world matters? When “what happened before what” isn’t enough?</p>
<p>Can we bring the clock back, without breaking everything we just fixed?</p>
]]></content:encoded></item><item><title><![CDATA[Ordering ≠ time]]></title><description><![CDATA[After Part 1, if you are feeling a little betrayed, that’s reasonable. We learned that clocks drift, time can jump, machines disagree, and “now” is mostly a polite suggestion.
We took the most fundame]]></description><link>https://chidinma.dev/ordering-not-time</link><guid isPermaLink="true">https://chidinma.dev/ordering-not-time</guid><category><![CDATA[distributed systems]]></category><category><![CDATA[causality]]></category><category><![CDATA[System Design]]></category><category><![CDATA[happens-before]]></category><category><![CDATA[ordering]]></category><category><![CDATA[lamport]]></category><dc:creator><![CDATA[Chidinma]]></dc:creator><pubDate>Sun, 05 Apr 2026 13:24:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/9d389e20-657f-482a-85b9-64b8f5d3170b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After <a href="https://chidinma.dev/why-time-breaks-distributed-systems">Part 1</a>, if you are feeling a little betrayed, that’s reasonable. We learned that clocks drift, time can jump, machines disagree, and “now” is mostly a polite suggestion.</p>
<p>We took the most fundamental tool in our toolkit, the timestamp, and discovered it’s just a local guess wearing a fancy suit.</p>
<p>But this realisation leads us to a more interesting question: <strong>If we can't trust the clock to tell us the order of events, what <em>can</em> we trust?</strong></p>
<p>Before we answer that, here’s the twist: <strong>Ordering was never about time in the first place.</strong></p>
<h3>The "WhatsApp" revelation</h3>
<p>Think about the group chat on your phone.</p>
<p>If I send a message asking, "Who wants pizza?"<br />And you reply, "I do!"</p>
<p>The order matters.</p>
<p>If your reply arrives "before" my question, the conversation makes no sense.</p>
<p>But notice something important. We don’t care what time those messages were sent.</p>
<p>We care that:</p>
<ul>
<li><p>your reply came <em>after</em> my question,</p>
</li>
<li><p>and that it depended on it.</p>
</li>
</ul>
<p>When we say we want to order events, we don’t actually care about wall-clock time. We care about something much more specific: <strong>“Did one event have the chance to influence another?”</strong></p>
<p>We are not asking:</p>
<ul>
<li><p>“Which happened at 09:00:01?”</p>
</li>
<li><p>“Which machine had the smaller number?”</p>
</li>
</ul>
<p>But:</p>
<ul>
<li><p>Did event B know about event A?</p>
</li>
<li><p>Could B have seen A’s effects?</p>
</li>
<li><p>Was there a dependency between them?</p>
</li>
</ul>
<p>This is a question about <strong>causality</strong>, not clocks.</p>
<h3><strong>A distributed example</strong></h3>
<p>Imagine two machines. Machine A writes a value. Machine B later reads that value.</p>
<p>Even if both clocks are wrong, one thing must be true: The write happened before the read, in a way that mattered.</p>
<p>Not before in universal time. Before in <strong>causal reality</strong>.</p>
<p>The read depends on the write. That dependency <em>is</em> the ordering.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/b9c8bcf1-e700-43f8-98a5-72573942137c.png" alt="A diagram with two vertical lanes labeled Machine A and Machine B, with time flowing downward. On Machine A, an event labeled “Write (A)” appears higher on the timeline. A diagonal arrow labeled “message” points from the write event to Machine B. On Machine B, a lower event labeled “Read (B)” shows that the read occurs after receiving the message, indicating a causal dependency where the write happens-before the read." style="display:block;margin:0 auto" />

<blockquote>
<p>A write on Machine A causally precedes a read on Machine B via message passing.</p>
</blockquote>
<p>Now imagine something different:</p>
<ul>
<li><p>Machine A updates User 1’s profile.</p>
</li>
<li><p>Machine B updates User 2’s profile at roughly the same time.</p>
</li>
</ul>
<p>These two events may happen milliseconds apart. Or at the exact same time.</p>
<p>It doesn’t matter.</p>
<p>They are independent.</p>
<p>Neither could possibly have influenced the other.</p>
<p>Trying to force them into a single timeline doesn’t add clarity. It adds constraints.</p>
<h3><strong>Time answers the wrong question</strong></h3>
<p>Physical time tries to answer: “When did this happen relative to the universe?”. Distributed systems don’t need that. They need to answer: “Which events could have affected which other events?”</p>
<p>Two events can:</p>
<ul>
<li><p>have perfectly ordered timestamps,</p>
</li>
<li><p>look clean and sequential,</p>
</li>
<li><p>and still be completely unrelated.</p>
</li>
</ul>
<p>And two events can:</p>
<ul>
<li><p>look simultaneous,</p>
</li>
<li><p>have misleading timestamps,</p>
</li>
<li><p>and be tightly linked by cause and effect.</p>
</li>
</ul>
<p>Time can’t tell the difference. But the relationship between the events can.</p>
<h3>The shift</h3>
<p>This is the part where the ground finally stops moving. Soon, you will understand that ordering isn’t about placing everything on a single global timeline. It’s about identifying <strong>dependencies</strong>.</p>
<p>Some events are connected. Others are independent.</p>
<ul>
<li><p>If event A caused event B, that relationship matters.</p>
</li>
<li><p>If A and B are unrelated, forcing an order between them is artificial. And, sometimes expensive.</p>
</li>
</ul>
<p>This is why distributed systems don’t start with clocks.</p>
<p>They start with a rule: If A could have influenced B, then A must come before B.</p>
<p>That’s the entire foundation.</p>
<p>And this rule has a name.</p>
<h2>Happens-Before: A language for causality</h2>
<p>It’s not a timestamp. It’s not a clock. It’s a relationship.</p>
<p>Back in 1978, Leslie Lamport made a simple observation that would fundamentally change how we reason about distributed systems.</p>
<p>We don’t actually need clocks to determine order.<br />We need to track how information flows.</p>
<p>He introduced a concept called <strong>happens-before</strong>. The idea is almost embarrassingly simple: If Event A could have influenced Event B, then A happened before B.</p>
<p>Lamport wrote it like this: <code>A → B</code> . Which simply means: <em>A happens-before B.</em></p>
<p>But forget the notation for a moment. The intuition is more important. There are only three ways we can say, with certainty, that one event came before another, without looking at a clock.</p>
<h3><strong>Order inside a single process</strong></h3>
<p>If a program executes:</p>
<ul>
<li><p>Step 1</p>
</li>
<li><p>Then, Step 2</p>
</li>
</ul>
<p>Step 1 happened before Step 2.</p>
<p>Not because of timestamps. Because programs execute instructions in sequence.</p>
<p>Within a single process or thread, order is clear.</p>
<p>If <code>a</code> and <code>b</code> are events in the same process, and <code>a</code> occurs before <code>b</code>, then <code>a → b</code></p>
<h3><strong>Message passing</strong></h3>
<p>Now, imagine I send you an email.</p>
<p>You read it. You reply.</p>
<p>Your reply could not exist without my original email.</p>
<p>Even if my server thinks it’s 09:00 and yours thinks it’s 08:00, the reply depends on the message.</p>
<p>The data flow proves the order.</p>
<p>Cause precedes effect.</p>
<p>If <code>a</code> is the sending of a message and <code>b</code> is the receipt of that same message, then <code>a → b</code></p>
<img src="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/7aa90fd2-3b33-4990-9fca-f3f3476af2bd.png" alt="A diagram with two vertical lanes labeled Machine A and Machine B, with time flowing downward. On Machine A, an event labeled “Send (A)” appears higher on the timeline. A blue arrow labeled “message” travels diagonally through a lightly shaded region labeled “network” toward Machine B. On Machine B, a lower event labeled “Receive (B)” shows that the receive happens after the message arrives, indicating a causal dependency from send to receive." style="display:block;margin:0 auto" />

<blockquote>
<p>A message sent from Machine A travels through the network and is received by Machine B, establishing a causal relationship where the receive depends on the send.</p>
</blockquote>
<h3><strong>Transitivity</strong></h3>
<p>This is the subtle part that actually makes the whole system work. It’s the "friend of a friend" rule for data.</p>
<p>If:</p>
<ul>
<li><p>A happened before B,</p>
</li>
<li><p>and B happened before C,</p>
</li>
</ul>
<p>Then A happened before C.</p>
<p>Even if A and C never directly communicated.</p>
<p>If <code>a → b</code> and <code>b → c</code> , then <code>a → c</code> .</p>
<p>This chaining of influence is what lets distributed systems reason about entire histories without ever asking, “What time was it?” If we can trace the path of messages, we don't need to guess about the timing. The relationship is set in stone.</p>
<h3><strong>What happens-before does not say</strong></h3>
<p>Happens-before does not force every event into a single timeline.</p>
<p>If two events:</p>
<ul>
<li><p>occur on different machines,</p>
</li>
<li><p>never exchange messages,</p>
</li>
<li><p>and never influence each other,</p>
</li>
</ul>
<p>Then neither happened-before the other.</p>
<p>They are <strong>concurrent</strong>.</p>
<p>Our instinct is to ask: which one came first? Which one “won”? Which one happened at 09:00:00.001?</p>
<p>But in a distributed system, concurrency isn’t an error or a calculation we haven’t finished yet.</p>
<p>It’s a fact. It’s the system admitting: <em>"These two events are strangers. Their order doesn't exist."</em></p>
<h3><strong>Causality</strong></h3>
<p>We can stop dancing around it. What we’ve really been talking about this whole time is called Causality.</p>
<p>If event A could have influenced event B, then A is a cause and B is an effect.</p>
<p>Happens-before is simply the formal way of capturing causal relationships. It’s a record of potential influence.</p>
<p>If there is no causal relationship between two events, then neither happened-before the other. The two events are independent and therefore concurrent.</p>
<p>And that distinction, causal vs independent, is the key to everything that comes next.</p>
<h3><strong>Why this changes everything</strong></h3>
<p>Here’s what changed. We replaced “What time is it?” with “What could this event have known?”</p>
<p>We traded timestamps with relationships.</p>
<p>And suddenly, ordering doesn’t depend on synchronised clocks, atomic hardware, or GPS satellites.</p>
<p>It depends on causality.</p>
<p>That’s the foundation.</p>
<h2>Partial Ordering</h2>
<p>Once you accept causality as the foundation, a trade-off appears: Not every pair of events can be compared.</p>
<p>In a world governed by happens-before, some events have a clear relationship:</p>
<ul>
<li><p>A caused B</p>
</li>
<li><p>B depends on A</p>
</li>
<li><p>Information flowed from A to B</p>
</li>
</ul>
<p>But other events have no relationship at all.</p>
<p>They happened on different machines. They never exchanged messages. They never influenced each other.</p>
<p>In this world, trying to say which one “came first” is meaningless.</p>
<h3><strong>Not everything needs to be ordered</strong></h3>
<p>In everyday life, we’re used to total order. We assume everything in the universe is waiting in a single line.</p>
<ul>
<li><p>Monday comes before Tuesday.</p>
</li>
<li><p>Page 3 comes before Page 4.</p>
</li>
<li><p>09:00 comes before 09:01.</p>
</li>
</ul>
<p>Everything fits neatly on a single axis.</p>
<p>However, distributed systems don’t work that way.</p>
<p>Instead of a single straight timeline, you get something more like a web. Some events are connected. Others happen independently.</p>
<p>Think of it like lineage. You can say your grandfather came before your father. But can you say your friend came before your cousin?</p>
<p>Of course not. They are independent.</p>
<p>If <code>A → B</code>, then A must come before B.</p>
<p>But if A and C are unrelated, there is no rule that says one must come before the other.</p>
<p>That’s a <strong>partial order</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/0eafb0a7-7dfa-486e-8c12-3a0c6eab989c.png" alt="A diagram showing multiple events as nodes connected by arrows forming a directed acyclic graph. Some nodes are connected through branching and merging paths, indicating causal relationships where one event influences another. Other nodes, such as G and I, are not connected to the graph, representing independent events that have no ordering relative to the others. The structure illustrates that not all events can be placed on a single timeline." style="display:block;margin:0 auto" />

<blockquote>
<p>A partial order of events: arrows show causal relationships, while isolated nodes represent independent events with no defined ordering.</p>
</blockquote>
<h3><strong>What “partial” really means</strong></h3>
<p>Partial doesn’t mean “incomplete” or “approximate.” It means: <strong>Only order what must be ordered.</strong></p>
<p>If causality demands that A precede B, respect it. If causality says nothing about A and C, leave them alone.</p>
<p>This is not a limitation. It’s precision. It’s freedom.</p>
<h3><strong>Concurrency</strong></h3>
<p>When two events have no happens-before relationship, we call them <strong>concurrent</strong>.</p>
<p>Contrary to popular belief, concurrency does not mean “at the same time.” It simply means there is no causal link between them. They could have happened seconds apart. They could have happened on opposite sides of the planet. They could even have identical timestamps.</p>
<p>If neither could have influenced the other, they are concurrent.</p>
<p>That’s not ambiguity. That’s independence.</p>
<h3><strong>Why partial ordering is powerful</strong></h3>
<p>Partial ordering is the "cheat code" of distributed systems. It preserves exactly what matters:</p>
<ul>
<li><p>It respects causality.</p>
</li>
<li><p>It avoids inventing artificial order.</p>
</li>
<li><p>It allows independent events to proceed freely without waiting for other events.</p>
</li>
</ul>
<p>This is why distributed systems prefer partial order whenever possible.</p>
<p>It maximises concurrency and minimises coordination. You don't need a "meeting" between two servers to decide who goes first if their work doesn't overlap.</p>
<p>But sometimes… we don't get that luxury.</p>
<p>Sometimes, we <em>need</em> a single line.</p>
<p>We need to decide who gets the last ticket to the concert or who spent the last dollar in a bank account.</p>
<p>In those moments, independence becomes a liability.</p>
<p>We need a way to take our causal web and flatten it into a single, reliable order.</p>
<h2><strong>Total ordering</strong></h2>
<p>Partial order is elegant. It respects causality. It avoids unnecessary constraints. It lets independent work stay independent. It stays close to reality.</p>
<p>But sometimes reality isn’t enough.</p>
<p>Sometimes we don’t just want to respect causality; we want everyone to agree on the exact same sequence of events. Even when causality doesn’t force one.</p>
<p>That’s <strong>total ordering</strong>.</p>
<h3><strong>What total order really means</strong></h3>
<p>A total order removes the "strangers" from the equation.</p>
<p>For any two events A and B, one <strong>must</strong> come before the other.</p>
<p>There are no unrelated events. No concurrency left undecided. No floating nodes in the graph.</p>
<p>Every event, no matter where it happened on the planet, gets a seat in a single, global line.</p>
<h3><strong>Why would we ever want that?</strong></h3>
<p>Because agreement is powerful.</p>
<p>Imagine a distributed database with three replicas. Two clients try to buy the last pair of sneakers at the same time.</p>
<ul>
<li><p>Replica 1 processes Buyer A, then Buyer B.</p>
</li>
<li><p>Replica 2 processes Buyer B, then Buyer A.</p>
</li>
</ul>
<p>Both replicas followed the rules of causality. Both are internally consistent.</p>
<p>And yet, they now disagree on who owns the shoes.</p>
<p>Same inputs. Different histories. Different state.</p>
<p>That’s a nightmare for a bank account, an inventory system, a file system, or a blockchain.</p>
<p>Nothing here is broken. Causality told us how the events were related, but it didn't give us a tie-breaker.</p>
<p>The system did exactly what we asked.</p>
<p>Which is the problem. It just wasn’t enough.</p>
<p>Total ordering forces every replica to say: <strong>“We will all process events in this exact order.”</strong></p>
<p>That shared order becomes the system’s truth.</p>
<h3><strong>The cost of forcing order</strong></h3>
<p>Here’s the caveat: <strong>You don’t get total order for free.</strong></p>
<p>If two events are concurrent, meaning causality says nothing about their order, the system has to invent one.</p>
<p>And inventing order requires:</p>
<ul>
<li><p>communication (talking to other machines).</p>
</li>
<li><p>coordination (agreeing on a winner).</p>
</li>
<li><p>waiting (latency).</p>
</li>
<li><p>failure handling.</p>
</li>
</ul>
<p>You pay for that agreement with latency and complexity.</p>
<p>This is why high-performance systems try to stay in the world of partial order for as long as possible, reaching for total order only when the business logic demands it.</p>
<p>Partial order is natural. Total order is <strong>negotiated</strong>.</p>
<h3>From web to line</h3>
<p>Partial order looks like a web. Total order flattens that web into a line.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/ac73272a-a36f-4365-a793-cd2285b1ce15.png" alt="" style="display:block;margin:0 auto" />

<blockquote>
<p>Flattening a partial order into a total order: causal relationships are preserved, but independent events are placed arbitrarily so every machine can agree on a single sequence.</p>
</blockquote>
<p>Some edges in that line are required by causality.</p>
<p>If A caused B, A <em>must</em> stay in front. But for independent events, the order is arbitrary. It’s chosen so that every machine can look at its history and see the same truth.</p>
<p>And that arbitrariness is fine as long as everyone chooses the same one.</p>
<h3><strong>Why this matters</strong></h3>
<p>You don’t choose one because it’s prettier. You choose based on what your system must guarantee.</p>
<p>Partial order maximises concurrency.</p>
<p>Total order maximises agreement.</p>
<p>If you need:</p>
<ul>
<li><p>strong consistency</p>
</li>
<li><p>deterministic replay</p>
</li>
<li><p>identical replicas</p>
</li>
</ul>
<p>You need <strong>total order. And total order doesn’t emerge naturally.</strong></p>
<p>It has to be negotiated.</p>
<p>That negotiation is where coordination protocols live. That’s where consensus begins.</p>
<h2>Takeaway</h2>
<p>Clocks can’t be trusted.</p>
<p>And ordering was never about time.</p>
<p>But systems still need to agree.</p>
<p>So how do you build a single global sequence… without relying on time?</p>
<p>That’s where the real work begins.</p>
]]></content:encoded></item><item><title><![CDATA[Why time breaks distributed systems]]></title><description><![CDATA[When ordering events, time seems like the most obvious thing to use. If something happened at 09:00 and another at 09:01, it is clear what the order is.
Right?
Wrong.
In distributed systems, this intu]]></description><link>https://chidinma.dev/why-time-breaks-distributed-systems</link><guid isPermaLink="true">https://chidinma.dev/why-time-breaks-distributed-systems</guid><category><![CDATA[time]]></category><category><![CDATA[distributed systems]]></category><category><![CDATA[System Design]]></category><category><![CDATA[Clock Synchronisation]]></category><category><![CDATA[Event Ordering]]></category><category><![CDATA[NTP ]]></category><category><![CDATA[monotonic clock]]></category><category><![CDATA[causality]]></category><dc:creator><![CDATA[Chidinma]]></dc:creator><pubDate>Sun, 29 Mar 2026 09:12:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/3134481d-b013-4d53-9e65-bb1b72b8da21.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When ordering events, time seems like the most obvious thing to use. If something happened at 09:00 and another at 09:01, it is clear what the order is.</p>
<p>Right?</p>
<p>Wrong.</p>
<p>In distributed systems, this intuition is wrong in ways that are subtle, surprising, and dangerous.</p>
<p>When I first started learning about distributed systems and the concept of "ordering", I had no idea how shaky the ground beneath us actually was. I assumed a timestamp was a fact.</p>
<p>I was wrong.</p>
<p>It turns out, time is less of a universal constant and more of a local, unreliable opinion.</p>
<p>Today, we’re going to learn the lesson that every distributed system eventually learns the hard way: <strong>Clocks lie</strong>. Sometimes they drift apart like two childhood friends losing touch. Sometimes they jump backwards without warning. And sometimes, they agree just long enough to trick you into trusting them, right before they break assumptions you didn’t even know you were making.</p>
<p>This series is about why clocks, as we know them, lie and why distributed systems had to invent entirely new ways to reason about order.</p>
<h1><strong>The lie of physical time</strong></h1>
<p>So what does it actually mean to say that clocks lie?</p>
<p>After all, your laptop has a clock. Your phone has a clock. Every machine has a clock. We have protocols like NTP (Network Time Protocol) to keep them in sync, and we even have atomic clocks in GPS satellites keeping time with absurd, terrifying precision.</p>
<p>Surely, "09:00:00" is "09:00:00" everywhere.</p>
<p>The problem isn’t that clocks are useless. The problem is that we quietly ask them to do more than they can safely promise.</p>
<p>We ask them to be a <strong>source of truth</strong> for the order of the universe, but they were only ever designed for human-facing timestamps, for example, to tell us when to go to lunch.</p>
<p>To understand where things start to break, we need to look at what “time” even means in a distributed system. In particular, we need to understand why physical time is a shaky foundation for any system that cares about the truth and about order.</p>
<p>That’s where we’ll start.</p>
<h2><strong>What we <em>expect</em> from physical time</strong></h2>
<p>When we look at a timestamp, we’re making three huge, silent assumptions:</p>
<ol>
<li><p>Precision: The clock is accurate down to the millisecond.</p>
</li>
<li><p>Monotonicity: The clock only moves forward.</p>
</li>
<li><p>Universality: Other machines agree with us.</p>
</li>
</ol>
<p>In a distributed system, <strong>all three are routinely violated.</strong></p>
<h2><strong>Assumption #1: Precision implies Accuracy</strong></h2>
<p>We tend to think that precision and accuracy are the same thing.</p>
<p>They aren't.</p>
<p>Precision is how many digits the clock gives you.<br />Accuracy is whether those digits are telling the truth.</p>
<p>When we see a timestamp like <code>09:00:00.123</code>, it feels definitive. Three decimal places. Millisecond resolution. That number looks confident. Scientific. Trustworthy.</p>
<p>But here’s an uncomfortable truth: precision is not the same thing as accuracy.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/8cb48b7b-9546-460f-b34d-69c230af5d31.png" alt="Precise timestamps clustered closely together but offset from the true time, contrasted with wider, less precise readings centred around the true time, showing that precision does not guarantee accuracy." style="display:block;margin:0 auto" />

<blockquote>
<p>Precision creates the illusion of certainty. Accuracy determines how close you are to the truth.</p>
</blockquote>
<p>Your system can happily print timestamps with nanosecond resolution while being wrong by tens of milliseconds - or more.</p>
<p>Precision is just the ability to say a number with a straight face; accuracy is the ability to actually be right.</p>
<p>Precision is easy. Accuracy is hard.</p>
<p>Most machines measure time by counting ticks from a local hardware clock (usually a tiny vibrating quartz crystal). Those ticks are frequent, so it’s easy to divide them up and label events with fine-grained timestamps.</p>
<p>That’s where the illusion comes from.</p>
<p>The clock says: <code>09:00:00.123</code> . What it really means is closer to: “As far as I can tell, it’s somewhere around 09:00, give or take.” The extra digits don’t make the clock more accurate. They just make it look more confident while lying to your face.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/7e990ac0-4a92-4e53-9261-80ab1005bef6.png" alt="A precise timestamp shown as a single point above a timeline, with a wider uncertainty window around the true time below it, illustrating that the reported time hides a range of possible actual times." style="display:block;margin:0 auto" />

<blockquote>
<p>A precise timestamp is just a single point hiding a range of possible truths.</p>
</blockquote>
<h3><strong>The hidden gap between clocks</strong></h3>
<p>Now imagine two machines sitting next to each other in the same data centre.</p>
<p>Both report millisecond-precise timestamps.<br />Both are “synchronised”.<br />Both look fine in dashboards and logs.</p>
<p>And yet, one might be 15ms ahead of the other.</p>
<p>Nothing is broken. No alarms. This is normal.</p>
<p>From the system’s point of view, both clocks are working as designed. From your point of view, the timestamps are already lying to you, just quietly.</p>
<h3><strong>Why this breaks ordering</strong></h3>
<p>If two events happen close together on different machines, precision gives you a false sense of certainty. You might look at this and think the order is obvious:</p>
<pre><code class="language-python">Machine A: 09:00:00.120
Machine B: 09:00:00.118
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/5521fcf4-07d6-45b5-9d4a-99c34ec423d3.png" alt="Two machine timelines each showing an event with uncertainty ranges that overlap, making it impossible to determine which event happened first despite slightly different timestamps." style="display:block;margin:0 auto" />

<blockquote>
<p>Two timestamps can suggest an order, even when reality makes that order unknowable.</p>
</blockquote>
<p>You would assume the event on Machine B happened first. But if Machine B’s clock is lagging by just 5ms, then in reality, A actually happened first.</p>
<p>The problem isn’t that the clocks are "sloppy." The problem is that “accurate to the millisecond” was never a promise they could safely keep. When we rely on these numbers for ordering, we implicitly assume: “If two times differ by a few milliseconds, the order is real.”</p>
<p>In a distributed system, that assumption is a gamble, and the house always wins.</p>
<p>Precision gives us numbers. Accuracy gives us truth. Physical clocks mostly just give us the numbers.</p>
<h2><strong>Assumption #2: Monotonicity</strong></h2>
<p>In the real world, time is relentless. It is a one-way trip. You can’t unspill milk, and you can’t un-tick a second. We build our software logic on this basic law of the universe: Forward-ever, backwards-never!</p>
<p>But there’s a quieter, more dangerous assumption hiding behind precision: the belief that once time moves forward, it stays there.</p>
<p>We assume that if an event happened at 09:00:01, then nothing in the future can ever happen at 09:00:00. This feels so fundamental that we rarely question it. After all, time doesn’t go backwards.</p>
<p>We build systems on that assumption.</p>
<p>On a single machine, under ideal conditions, this mostly holds. In real systems, it absolutely does not.</p>
<h3><strong>Time can go backwards</strong></h3>
<p>Let’s start with the most unsettling case.</p>
<p>Clocks can, and do, move backwards. This happens whenever the system decides its idea of "now" is wrong and tries to fix it. For example:</p>
<ul>
<li><p>a clock synchronisation correction</p>
</li>
<li><p>a virtual machine pause and resume</p>
</li>
<li><p>a leap second adjustment</p>
</li>
<li><p>a manual clock change by an administrator</p>
</li>
</ul>
<p>The system doesn’t rewind history. It simply updates its opinion of “now”.</p>
<p>From the clock’s point of view, that looks like this:</p>
<pre><code class="language-python">09:00:01.500
09:00:01.200   &lt;- time went backwards!
</code></pre>
<p>Nothing crashed. Nothing is “broken”. But any code assuming monotonic time just lost its footing.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/aba0ca88-dd31-4bee-b77b-aa67e9acd571.png" alt="A wall-clock timeline where time moves forward and then suddenly jumps backward, showing a later timestamp followed by an earlier one due to clock adjustment." style="display:block;margin:0 auto" />

<blockquote>
<p>Wall-clock time can jump backwards, breaking the assumption that time always moves forward.</p>
</blockquote>
<h3><strong>When time doesn’t go backwards, it “slews”</strong></h3>
<p>To avoid sudden jumps, many systems try to be clever. Instead of stepping the clock backwards, they slow it down until reality catches up. Time still moves forward, just not at the rate you expect.</p>
<p>This is called <strong>slewing.</strong></p>
<p>From the outside, everything looks fine. Timestamps are increasing. But durations stretch. Timeouts fire late. Delays feel inconsistent. Imagine doing a plank for 1 minute, and it feels longer!</p>
<p>From the system’s point of view:</p>
<ul>
<li><p>One second is no longer one second</p>
</li>
<li><p>“Now” is a moving target</p>
</li>
</ul>
<p>Time didn’t jump. It quietly changed speed. It just changed the rules of physics while you weren't looking.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/8814eadb-c7a5-411c-8aff-083c694e1cc4.png" alt="A wall-clock timeline that slows down and gradually bends to catch up with true time, showing lag without any backward jump." style="display:block;margin:0 auto" />

<blockquote>
<p>When clocks don’t jump, they drift - silently stretching time instead.</p>
</blockquote>
<h3><strong>The Great Divide: Wall-Clock Time vs. Monotonic Time</strong></h3>
<p>At this point, most operating systems quietly admit defeat.</p>
<p>They expose two clocks:</p>
<ol>
<li><p>Wall-clock time: Meant for humans. Can jump. Can slew. Can go backwards. It’s for knowing when to send that ”Happy New Year” text.</p>
</li>
<li><p>Monotonic time: Meant for measuring durations. Never goes backwards. Has no date. No timezone. No meaning outside the process. It’s just a counter that goes up. It doesn’t know if it is Tuesday or what year it is. It just knows it’s been ticking since the machine woke up.</p>
</li>
</ol>
<p>Monotonic time answers: “How much time has passed?” “How long did that take?”</p>
<p>Wall-clock time answers: “What time do humans think it is?”</p>
<p><strong>Confusing the two is one of the easiest ways to break a system.</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/26bcc00b-7c34-4001-9ed5-a9b525097b95.png" alt="Side-by-side comparison of wall-clock time, which can jump backwards due to adjustments, and monotonic time, which increases steadily without going backwards." style="display:block;margin:0 auto" />

<blockquote>
<p>Wall-clock time tells you <em>what time it is</em>. Monotonic time tells you <em>how time has passed</em>.</p>
</blockquote>
<h3><strong>Why this breaks ordering</strong></h3>
<p>Ordering assumes a simple rule: Later events have later timestamps. But once time can jump or slow down, that rule collapses. An event can:</p>
<ol>
<li><p>Suddenly appear to happen before the thing that caused it</p>
</li>
<li><p>Get a timestamp earlier than a previous event</p>
</li>
<li><p>Violate “read after write” assumptions</p>
</li>
</ol>
<p>The problem isn’t bad engineering. It’s that monotonicity was never a guarantee that wall-clock time could make.</p>
<p>Once you notice this, it’s hard to un-see it. <em><strong>Precision lied quietly. Monotonicity lies loudly.</strong></em></p>
<p>If time can’t even be trusted to move forward on one machine, the idea that multiple machines could ever agree on a global “now” should start to feel very suspicious.</p>
<p>Which brings us to our final, and most impossible, assumption.</p>
<h2><strong>Assumption #3: Universality</strong></h2>
<p>We’ve already faced two uncomfortable truths: clocks aren’t as accurate as they look, and even on a single machine, time doesn’t reliably move forward.</p>
<p>And yet, there’s usually one final assumption quietly holding our sanity together: The Backup Plan. We assume that even if my local clock is a little bit of a mess, the system as a whole is at least roughly on the same page. <strong>We believe in a shared, global “now.”</strong></p>
<p>This assumption feels reasonable. We have GPS satellites, atomic clocks, and NTP (Network Time Protocol) servers. Surely, modern infrastructure can keep a handful of machines within a few milliseconds of each other. That should be good enough, right?</p>
<p>It isn’t.</p>
<h3><strong>There is no global clock</strong></h3>
<p>In a distributed system, "Now" isn't a fact. It is a local, subjective experience. Every machine is an island with its own clock.</p>
<p>Those clocks:</p>
<ul>
<li><p>Tick at slightly different speeds (thanks to factors such as manufacturing differences, temperature, humidity and air pressure)</p>
</li>
<li><p>Drift apart over time</p>
</li>
<li><p>Are corrected asynchronously</p>
</li>
<li><p>Receive updates over unreliable networks</p>
</li>
</ul>
<p>There is no central authority whispering the true time into every CPU at the same instant. What we call “clock synchronisation” is really just a best-effort agreement that is being continuously negotiated and violated the moment it’s made.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66065f84e6d59b47347f34f8/a648f576-26ce-4f92-871a-5fd135562458.png" alt="Three machine clocks drifting apart over time, each showing a different timestamp at the same real moment, illustrating that synchronised systems can still disagree." style="display:block;margin:0 auto" />

<blockquote>
<p>Clocks can be “in sync” and still disagree.</p>
</blockquote>
<h3><strong>Synchronisation doesn’t mean agreement</strong></h3>
<p>Protocols like NTP don’t say: “All machines now have the same time.” They say something like “Your clock is probably within <em>some</em> bounds of real time.”</p>
<p>That bound varies and is often unknown at the moment you read the clock. It can be temporarily blown out by a spike in network traffic or a busy CPU. Two machines can both be perfectly "healthy" and "in sync" and still disagree by 20 milliseconds. In a world where a database can process thousands of transactions in a single millisecond, a 20ms gap is a canyon where assumptions go to die.</p>
<h3><strong>The "God's Eye View" is a Fantasy</strong></h3>
<p>Even if clocks were perfect (they aren’t), messages are not.</p>
<p>When Machine A sends a message to Machine B:</p>
<ul>
<li><p>it leaves at some time</p>
</li>
<li><p>arrives later</p>
</li>
<li><p>after an unpredictable delay</p>
</li>
</ul>
<p>By the time B sees the message, A’s idea of “now” may already be outdated.</p>
<p>So when B compares:</p>
<ul>
<li><p>“the time in the message”</p>
</li>
<li><p>with “my current time”</p>
</li>
</ul>
<p>It’s comparing two <strong>local opinions</strong>, separated by uncertainty and delay.</p>
<h3><strong>Why this breaks ordering completely</strong></h3>
<p>Ordering is supposed to answer a simple, vital question: Did this happen before that? Did event A happen before event B in a way that mattered?</p>
<p>Timestamps look like they answer this perfectly. Smaller number first, bigger number later. Case closed.</p>
<p>But once you accept that clocks aren’t precise, time isn’t monotonic, and machines don’t agree on “now,” you realise that timestamps have stopped answering the question you’re actually asking.</p>
<p>They’ve started answering a much less useful question: “Which machine had a slightly higher number on its local, unreliable crystal at the moment this happened?”</p>
<p>When you rely on those numbers, you're building on sand. Two events can have timestamps that suggest a clear order, while the physical reality of the system supports the exact opposite.</p>
<p>And the worst part? There is no way to tell which is correct.</p>
<p>At that point, ordering based on time isn't just fragile, it's logically meaningless. It’s like trying to measure the distance between two moving cars using a rubber band.</p>
<p>This isn’t a bug. This isn’t a misconfiguration. This is just physics, networks, and independent machines doing exactly what they were built to do.</p>
<h2><strong>Takeaway</strong></h2>
<p><strong>Precision</strong> failed quietly.</p>
<p><strong>Monotonicity</strong> failed loudly.</p>
<p><strong>Universality</strong> never existed in the first place.</p>
<p>There is no shared global time in a distributed system, only local clocks, imperfectly stitched together. And once you accept that, you get to the realisation that <strong>Time is the wrong tool for reasoning about order.</strong></p>
<p>Which raises the obvious question: <strong>If we can’t safely use time to order events, what on earth do we do instead?</strong></p>
]]></content:encoded></item><item><title><![CDATA[Introduction to Distributed Transactions]]></title><description><![CDATA[Transactions
Before we look into distributed transactions, it is important we understand transactions.
What is a transaction?
A transaction is a set of operations executed as a single, indivisible unit of work on a database ensuring ACID (Atomicity, ...]]></description><link>https://chidinma.dev/introduction-to-distributed-transactions</link><guid isPermaLink="true">https://chidinma.dev/introduction-to-distributed-transactions</guid><category><![CDATA[distributed-transactions]]></category><category><![CDATA[2PC]]></category><category><![CDATA[three-phase-commit]]></category><category><![CDATA[atomic-commit]]></category><category><![CDATA[transactions]]></category><category><![CDATA[acid]]></category><category><![CDATA[ACID Transactions]]></category><category><![CDATA[two-phase-commit]]></category><category><![CDATA[Databases]]></category><category><![CDATA[distributed system]]></category><dc:creator><![CDATA[Chidinma]]></dc:creator><pubDate>Wed, 29 May 2024 22:00:00 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-transactions">Transactions</h1>
<p>Before we look into distributed transactions, it is important we understand transactions.</p>
<h3 id="heading-what-is-a-transaction">What is a transaction?</h3>
<p>A transaction is a set of operations executed as a single, indivisible unit of work on a database ensuring ACID (Atomicity, Consistency, Isolation, and Durability) properties. This simply means that a transaction is a way for an application to group several reads and writes together into a logical unit. The entire transaction either succeeds (commit) or fails (abort). If the entire transaction fails, it can be retried without having to worry about partial failures - for example, cases where some reads and writes succeed but others fail - ensuring that either all operations succeed or none do.</p>
<h3 id="heading-why-are-transactions-important">Why are transactions important?</h3>
<p>Transactions are important because they provide safety guarantees through the ACID properties, which stand for Atomicity, Consistency, Isolation, and Durability. This acronym was coined by <a target="_blank" href="https://dl.acm.org/doi/pdf/10.1145/289.291">Theo Härder and Andreas Reuter</a> in 1983.</p>
<h2 id="heading-atomicity">Atomicity</h2>
<p>Recall that an atom is the smallest unit of matter that retains the properties of an element. In that same vein, atomicity is an indivisible unit of work in a transaction. Just as an atom cannot be split without changing its essential nature, a transaction's atomicity ensures that all operations within it are treated as a single, indivisible unit. Atomicity guarantees that either all of the operations in a transaction are completed or none of them take effect. If any part of the transaction fails, the entire transaction fails and is rolled back, maintaining the system's integrity. Atomicity ensures that we do not encounter partial failure or partial complete scenarios; the transaction either fully succeeds or fully fails.</p>
<h3 id="heading-scenario">Scenario</h3>
<p>For example, let's consider a banking system where a user wants to transfer €100 from Account A to Account B. This transaction involves two operations:</p>
<ol>
<li><p>Debit €100 from Account A,</p>
</li>
<li><p>Credit €100 to Account B.</p>
</li>
</ol>
<p><strong>Scenario 1: Successful Transaction</strong></p>
<ul>
<li><p>Begin Transaction</p>
</li>
<li><p>€100 is debited from Account A.</p>
</li>
<li><p>€100 is credited to Account B.</p>
</li>
<li><p>The transaction is committed.</p>
</li>
</ul>
<p>After the transaction is committed, the balances in the two account are as follows:</p>
<ol>
<li><p><strong>Account A</strong>: Original balance - €100</p>
</li>
<li><p><strong>Account B</strong>: Original balance + €100</p>
</li>
</ol>
<pre><code class="lang-sql"><span class="hljs-keyword">BEGIN</span> <span class="hljs-keyword">TRANSACTION</span>;

<span class="hljs-comment">-- Deduct €100 from Account A</span>
<span class="hljs-keyword">UPDATE</span> accounts <span class="hljs-keyword">SET</span> balance = balance - <span class="hljs-number">100</span> <span class="hljs-keyword">WHERE</span> <span class="hljs-keyword">id</span> = <span class="hljs-string">'A'</span>;
<span class="hljs-comment">-- Add €100 to Account B</span>
<span class="hljs-keyword">UPDATE</span> accounts <span class="hljs-keyword">SET</span> balance = balance - <span class="hljs-number">100</span> <span class="hljs-keyword">WHERE</span> <span class="hljs-keyword">id</span> = <span class="hljs-string">'B'</span>;

<span class="hljs-keyword">COMMIT</span>; <span class="hljs-comment">-- Finalize transaction</span>
</code></pre>
<p><strong>Scenario 2: Failed Transaction</strong></p>
<ul>
<li><p>Begin Transaction</p>
</li>
<li><p>€100 is debited from Account A.</p>
</li>
<li><p>An error occurs before crediting €100 to Account B (e.g., a network failure, system crash, or validation error).</p>
</li>
<li><p>The transaction is rolled back.</p>
</li>
</ul>
<p>After the transaction is rolled back, the balances are as follows:</p>
<ol>
<li><p><strong>Account A:</strong> Original balance (no change, since the debit operation was undone).</p>
</li>
<li><p><strong>Account B:</strong> Original balance (no change, since the credit operation never occurred).</p>
</li>
</ol>
<pre><code class="lang-sql"><span class="hljs-keyword">BEGIN</span> <span class="hljs-keyword">TRANSACTION</span>;

<span class="hljs-comment">-- Deduct €100 from Account A</span>
<span class="hljs-keyword">UPDATE</span> accounts <span class="hljs-keyword">SET</span> balance = balance - <span class="hljs-number">100</span> <span class="hljs-keyword">WHERE</span> account_id = <span class="hljs-string">'A'</span>;
<span class="hljs-comment">-- An error occurs here, so the next line is never executed</span>
<span class="hljs-keyword">UPDATE</span> accounts <span class="hljs-keyword">SET</span> balance = balance + <span class="hljs-number">100</span> <span class="hljs-keyword">WHERE</span> account_id = <span class="hljs-string">'B'</span>;

<span class="hljs-keyword">ROLLBACK</span>; <span class="hljs-comment">-- Undo all operations</span>
</code></pre>
<p>Without atomicity, transactions can result in partial updates, leading to data inconsistencies and integrity issues.</p>
<h2 id="heading-consistency">Consistency</h2>
<p>Consistency guarantees that a transaction transitions the database from a valid state to another while maintaining the databases' predefined rules. These predefined rules are specific to the application and they define what constitutes a valid state.</p>
<p>While the database guarantees Atomicity, Isolation, and Durability, Consistency is primarily the responsibility of the application. The database can enforce constraints and triggers (e.g., foreign keys, unique constraints), but it is up to the application to define and uphold the validity of the data. An example of an application defined constraint is: "account balances cannot be negative" or "inventory levels cannot be below zero". We can say that while the database provides the mechanisms to maintain atomicity, isolation, and durability, both the application and the database share the responsibility of maintaining consistency.</p>
<h3 id="heading-scenario-1">Scenario</h3>
<p>For example, let's process a product purchase. The goal is to ensure that the stock levels do not go negative when reducing stock and recording the sale.</p>
<p>Assume we have two tables: Products and Sales</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> Products (
    ProductID <span class="hljs-built_in">INT</span> PRIMARY <span class="hljs-keyword">KEY</span>,
    ProductName <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">255</span>),
    Stock <span class="hljs-built_in">INT</span>
);

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> Sales (
    SaleID <span class="hljs-built_in">INT</span> PRIMARY <span class="hljs-keyword">KEY</span> AUTO_INCREMENT,
    ProductID <span class="hljs-built_in">INT</span>,
    Quantity <span class="hljs-built_in">INT</span>,
    SaleDate DATETIME,
    <span class="hljs-keyword">FOREIGN</span> <span class="hljs-keyword">KEY</span> (ProductID) <span class="hljs-keyword">REFERENCES</span> Products(ProductID)
);
</code></pre>
<pre><code class="lang-sql"><span class="hljs-comment">-- Start the transaction</span>
<span class="hljs-keyword">BEGIN</span> <span class="hljs-keyword">TRANSACTION</span>;

<span class="hljs-comment">-- Check stock level</span>
<span class="hljs-keyword">SET</span> @stock_level = (<span class="hljs-keyword">SELECT</span> Stock <span class="hljs-keyword">FROM</span> Products <span class="hljs-keyword">WHERE</span> ProductID = <span class="hljs-number">1</span>);

IF @stock_level &lt; 20 THEN
    <span class="hljs-comment">-- Rollback the transaction if stock level is insufficient</span>
    <span class="hljs-keyword">ROLLBACK</span>;
ELSE
    <span class="hljs-comment">-- Reduce stock</span>
    <span class="hljs-keyword">UPDATE</span> Products
    <span class="hljs-keyword">SET</span> Stock = Stock - <span class="hljs-number">20</span>
    <span class="hljs-keyword">WHERE</span> ProductID = <span class="hljs-number">1</span>;

    <span class="hljs-comment">-- Record the sale</span>
    <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> Sales (ProductID, Quantity, SaleDate) <span class="hljs-keyword">VALUES</span> (<span class="hljs-number">1</span>, <span class="hljs-number">20</span>, <span class="hljs-keyword">NOW</span>());

    <span class="hljs-comment">-- Commit the transaction</span>
    <span class="hljs-keyword">COMMIT</span>;
<span class="hljs-keyword">END</span> <span class="hljs-keyword">IF</span>;
</code></pre>
<p><strong>Scenario 1: Successful Transaction</strong></p>
<ul>
<li><p>Begin Transaction</p>
</li>
<li><p>Check stock level.</p>
</li>
<li><p>The stock level is sufficient, reduce stock.</p>
</li>
<li><p>Record the sale in the sales table.</p>
</li>
<li><p>The transaction is committed.</p>
</li>
</ul>
<p><strong>Scenario 2: Failed Transaction</strong></p>
<ul>
<li><p>Begin Transaction</p>
</li>
<li><p>Check stock level.</p>
</li>
<li><p>The stock level is insufficient, roll back the transaction.</p>
</li>
</ul>
<p>Without consistency, data integrity rules may be violated, causing invalid data states.</p>
<h2 id="heading-isolation">Isolation</h2>
<p>Isolation ensures that transactions are executed independently without interference. Each transaction appears as if it is the only one running, preventing concurrent transactions from affecting each other’s states.</p>
<p>Consider an online banking system. Two users, Nemi and Shalini, are transferring money from their accounts to a shared savings account simultaneously. Isolation guarantees that Nemi's transaction doesn't see Shalini's transaction in progress and vice versa. If Nemi transfers €400 and Shalini transfers €200, isolation ensures the final balance reflects both transactions correctly, without either transaction interfering with the other. Without isolation, if Nemi's transaction reads the balance while Shalini's transaction is halfway, it might result in incorrect final balances.</p>
<p>As you can imagine, in large systems with busy databases, isolation becomes crucial as multiple transactions occur concurrently.</p>
<p>Isolation solves several problems such as:</p>
<ul>
<li><p><strong>Preventing Dirty Reads</strong>. This happens when transactions read uncommitted changes from other transactions. Preventing dirty reads avoids potential errors from temporary data.</p>
</li>
<li><p><strong>Preventing Non-Repeatable Reads</strong>: Ensures that once a transaction reads a value, it will see the same value if it reads again, preventing inconsistencies from concurrent updates.</p>
</li>
<li><p><strong>Preventing Phantom Reads</strong>: Prevents new rows from being added or existing rows from being deleted by other transactions during a transaction, ensuring stable query results.</p>
</li>
<li><p><strong>Preventing Lost Updates</strong>: Prevents multiple transactions from overwriting each other's changes.</p>
</li>
<li><p><strong>Preventing Inconsistent Retrievals</strong>: Ensures consistent data retrieval by isolating transactions.</p>
</li>
<li><p><strong>Preventing Uncommitted Data Visibility</strong>: Prevents transactions from seeing intermediate states of other transactions, ensuring only committed data is visible.</p>
</li>
</ul>
<h3 id="heading-levels-of-isolation">Levels of Isolation</h3>
<p>Isolation levels control the degree to which the transactions are isolated from each other. There are four main levels:</p>
<ol>
<li><p><strong>Read Uncommitted</strong>: Transactions can see uncommitted changes made by other transactions. This level offers the fastest isolation level due to minimal locking... a transaction doesn't need to wait for other transactions to finish. Another pro is low resource consumption. However, this can lead to <strong>dirty reads</strong> which in turn leads to data inconsistencies and anomalies.</p>
<p> <strong>Scenario</strong>: Account balance is €250. One transaction reads data that is being modified by another uncommitted transaction (Dirty read).</p>
<pre><code class="lang-sql"> <span class="hljs-comment">-- Transaction 1: Update account (but don't commit yet)</span>
 <span class="hljs-keyword">SET</span> <span class="hljs-keyword">TRANSACTION</span> <span class="hljs-keyword">ISOLATION</span> <span class="hljs-keyword">LEVEL</span> <span class="hljs-keyword">READ</span> UNCOMMITTED;
 <span class="hljs-keyword">BEGIN</span> <span class="hljs-keyword">TRANSACTION</span>;
 <span class="hljs-keyword">UPDATE</span> accounts <span class="hljs-keyword">SET</span> balance = balance - <span class="hljs-number">100</span> <span class="hljs-keyword">WHERE</span> account_id = <span class="hljs-number">10</span>;

 <span class="hljs-comment">-- Transaction 2: Read the uncommitted data</span>
 <span class="hljs-keyword">SET</span> <span class="hljs-keyword">TRANSACTION</span> <span class="hljs-keyword">ISOLATION</span> <span class="hljs-keyword">LEVEL</span> <span class="hljs-keyword">READ</span> UNCOMMITTED;
 <span class="hljs-keyword">BEGIN</span> <span class="hljs-keyword">TRANSACTION</span>;
 <span class="hljs-keyword">SELECT</span> balance <span class="hljs-keyword">FROM</span> accounts <span class="hljs-keyword">WHERE</span> account_id = <span class="hljs-number">10</span>; <span class="hljs-comment">-- Can see uncommitted changes</span>
 <span class="hljs-comment">-- Output: 150.00 (uncommitted data)</span>
 <span class="hljs-keyword">COMMIT</span>;

 <span class="hljs-comment">-- Transaction 1: Commit the update</span>
 <span class="hljs-keyword">COMMIT</span>;
</code></pre>
</li>
<li><p><strong>Read Committed</strong>: Transactions can only see changes committed by other transactions. This level of isolation provides better performance than higher isolation levels due to fewer locks. <strong>Prevents dirty reads</strong> (only committed changes are visible) <strong>but allows non-repeatable reads</strong> and <strong>phantom reads</strong></p>
<p> <strong>Scenario</strong>: Account balance is €250. A transaction reads only committed data, preventing dirty reads.</p>
<pre><code class="lang-sql"> <span class="hljs-comment">-- Transaction 1: Update account (but don't commit yet)</span>
 <span class="hljs-keyword">SET</span> <span class="hljs-keyword">TRANSACTION</span> <span class="hljs-keyword">ISOLATION</span> <span class="hljs-keyword">LEVEL</span> <span class="hljs-keyword">READ</span> COMMITTED;
 <span class="hljs-keyword">BEGIN</span> <span class="hljs-keyword">TRANSACTION</span>;
 <span class="hljs-keyword">UPDATE</span> accounts <span class="hljs-keyword">SET</span> balance = balance - <span class="hljs-number">100</span> <span class="hljs-keyword">WHERE</span> account_id = <span class="hljs-number">10</span>;

 <span class="hljs-comment">-- Transaction 2: Read the committed data</span>
 <span class="hljs-keyword">SET</span> <span class="hljs-keyword">TRANSACTION</span> <span class="hljs-keyword">ISOLATION</span> <span class="hljs-keyword">LEVEL</span> <span class="hljs-keyword">READ</span> COMMITTED;
 <span class="hljs-keyword">BEGIN</span> <span class="hljs-keyword">TRANSACTION</span>;
 <span class="hljs-keyword">SELECT</span> balance <span class="hljs-keyword">FROM</span> accounts <span class="hljs-keyword">WHERE</span> account_id = <span class="hljs-number">10</span>; <span class="hljs-comment">-- Sees only committed changes</span>
 <span class="hljs-comment">-- Output: 250.00 ( original committed data)</span>
 <span class="hljs-keyword">COMMIT</span>;

 <span class="hljs-comment">-- Transaction 1: Commit the update</span>
 <span class="hljs-keyword">COMMIT</span>;

 <span class="hljs-comment">-- Transaction 2: Read the now committed data</span>
 <span class="hljs-keyword">BEGIN</span> <span class="hljs-keyword">TRANSACTION</span>;
 <span class="hljs-keyword">SELECT</span> balance <span class="hljs-keyword">FROM</span> accounts <span class="hljs-keyword">WHERE</span> account_id = <span class="hljs-number">10</span>;
 <span class="hljs-comment">-- Output: 150.00 (committed data after Transaction 1)</span>
 <span class="hljs-keyword">COMMIT</span>;
</code></pre>
</li>
<li><p><strong>Repeatable Read</strong>: Ensures that if a transaction reads a value, it will see the same value if it reads again. This isolation level ensures consistent data within a transaction and provides higher level of data consistency compared to Read Committed. It <strong>prevents non-repeatable reads but allows phantom reads.</strong> This level also engages in more locking than Read Committed, potentially reducing performance.</p>
<p> <strong>Scenario</strong>: Account balance is €250. A transaction reads the same data multiple times and gets the same result each time, preventing non-repeatable reads.</p>
<pre><code class="lang-sql"> <span class="hljs-comment">-- Transaction 1: Start and read data</span>
 <span class="hljs-keyword">SET</span> <span class="hljs-keyword">TRANSACTION</span> <span class="hljs-keyword">ISOLATION</span> <span class="hljs-keyword">LEVEL</span> REPEATABLE <span class="hljs-keyword">READ</span>;
 <span class="hljs-keyword">BEGIN</span> <span class="hljs-keyword">TRANSACTION</span>;
 <span class="hljs-keyword">SELECT</span> balance <span class="hljs-keyword">FROM</span> accounts <span class="hljs-keyword">WHERE</span> account_id = <span class="hljs-number">10</span>; <span class="hljs-comment">-- Reads initial balance</span>
 <span class="hljs-comment">-- Output: 250.00</span>

 <span class="hljs-comment">-- Transaction 1: Update balance but do not commit yet</span>
 <span class="hljs-keyword">UPDATE</span> accounts <span class="hljs-keyword">SET</span> balance = balance - <span class="hljs-number">100</span> <span class="hljs-keyword">WHERE</span> account_id = <span class="hljs-number">10</span>;
 <span class="hljs-comment">-- The balance in the database is now 150, but Transaction 1 has not committed yet</span>

 <span class="hljs-comment">-- Transaction 2: Start and read data</span>
 <span class="hljs-keyword">SET</span> <span class="hljs-keyword">TRANSACTION</span> <span class="hljs-keyword">ISOLATION</span> <span class="hljs-keyword">LEVEL</span> REPEATABLE <span class="hljs-keyword">READ</span>;
 <span class="hljs-keyword">BEGIN</span> <span class="hljs-keyword">TRANSACTION</span>;
 <span class="hljs-keyword">SELECT</span> balance <span class="hljs-keyword">FROM</span> accounts <span class="hljs-keyword">WHERE</span> account_id = <span class="hljs-number">10</span>; <span class="hljs-comment">-- Should see the initial balance</span>
 <span class="hljs-comment">-- Output: 250.00 (Repeatable Read ensures this)</span>

 <span class="hljs-comment">-- Transaction 1: Commit the update</span>
 <span class="hljs-keyword">COMMIT</span>;

 <span class="hljs-comment">-- Transaction 2: Read data again within the same transaction</span>
 <span class="hljs-keyword">SELECT</span> balance <span class="hljs-keyword">FROM</span> accounts <span class="hljs-keyword">WHERE</span> account_id = <span class="hljs-number">10</span>;
 <span class="hljs-comment">-- Output: 250.00 (Repeatable Read ensures this)</span>

 <span class="hljs-comment">-- Transaction 2: Commit to end the transaction</span>
 <span class="hljs-keyword">COMMIT</span>;

 <span class="hljs-comment">-- Transaction 2: Start a new transaction and read data again</span>
 <span class="hljs-keyword">START</span> <span class="hljs-keyword">TRANSACTION</span>;
 <span class="hljs-keyword">SELECT</span> balance <span class="hljs-keyword">FROM</span> accounts <span class="hljs-keyword">WHERE</span> account_id = <span class="hljs-number">10</span>; 
 <span class="hljs-comment">-- Output: 150.00 (Reflects the committed update from Transaction 1)</span>
 <span class="hljs-keyword">COMMIT</span>;
</code></pre>
</li>
<li><p><strong>Serializable</strong>: Transactions are completely isolated from each other, ensuring full consistency. This isolation level <strong>prevents dirty reads, non-repeatable reads, and phantom reads.</strong> However, it is the slowest isolation level due to extensive locking and blocking, and it has the highest resource consumption, leading to potential bottlenecks.</p>
<p> <strong>Scenario</strong>: Account balance is €250. A transaction is fully isolated from the other ensuring the highest level of isolation, preventing phantom reads and ensuring data consistency across transactions.</p>
<pre><code class="lang-sql"> <span class="hljs-comment">-- Transaction 1</span>
 <span class="hljs-keyword">SET</span> <span class="hljs-keyword">TRANSACTION</span> <span class="hljs-keyword">ISOLATION</span> <span class="hljs-keyword">LEVEL</span> <span class="hljs-keyword">SERIALIZABLE</span>;
 <span class="hljs-keyword">BEGIN</span> <span class="hljs-keyword">TRANSACTION</span>;
 <span class="hljs-keyword">SELECT</span> balance <span class="hljs-keyword">FROM</span> accounts <span class="hljs-keyword">WHERE</span> account_id = <span class="hljs-number">1</span>; <span class="hljs-comment">-- Reads initial balance</span>
 <span class="hljs-keyword">UPDATE</span> accounts <span class="hljs-keyword">SET</span> balance = balance - <span class="hljs-number">100</span> <span class="hljs-keyword">WHERE</span> account_id = <span class="hljs-number">10</span>;
 <span class="hljs-comment">-- Output: 150</span>

 <span class="hljs-comment">-- Transaction 2</span>
 <span class="hljs-keyword">SET</span> <span class="hljs-keyword">TRANSACTION</span> <span class="hljs-keyword">ISOLATION</span> <span class="hljs-keyword">LEVEL</span> <span class="hljs-keyword">SERIALIZABLE</span>;
 <span class="hljs-keyword">BEGIN</span> <span class="hljs-keyword">TRANSACTION</span>;
 <span class="hljs-keyword">SELECT</span> balance <span class="hljs-keyword">FROM</span> accounts <span class="hljs-keyword">WHERE</span> account_id = <span class="hljs-number">10</span>; <span class="hljs-comment">-- Waits until Transaction 1 completes</span>

 <span class="hljs-comment">-- Transaction 1: Commit the transaction</span>
 <span class="hljs-keyword">COMMIT</span>;

 <span class="hljs-comment">-- Transaction 2: Now it can proceed</span>
 <span class="hljs-comment">-- Output: 150</span>
 <span class="hljs-keyword">COMMIT</span>;
</code></pre>
</li>
</ol>
<h2 id="heading-durability">Durability</h2>
<p>Durability guarantees that once a transaction has been committed, its changes are permanently recorded in the database, even in the event of system failures.This means that the changes made by the transaction are saved to a non-volatile storage, such as HDDs or SSDs.</p>
<h3 id="heading-mechanisms-ensuring-durability">Mechanisms Ensuring Durability</h3>
<p>Many systems guarantee durability through mechanisms such as:</p>
<ul>
<li><p><strong>Write-Ahead Logging (WAL):</strong> Before applying them to the database, the system uses a type of log known as the write-ahead log to log changes. This log ensures data consistency and prevents corruption during crashes. However, write-ahead logging (WAL) requires additional storage for logs and can introduce performance overhead.</p>
</li>
<li><p><strong>Checkpointing:</strong> The system provides recovery points by saving database states to disk periodically. This reduces the recovery time after a crash, but it can consume significant storage space and may lead to data loss between snapshots.</p>
</li>
<li><p><strong>Redundant Storage</strong>: The system protects against hardware failures by duplicating data across multiple storage devices. Techniques such as replication, regular backups, and RAID 1 (mirroring) are employed to ensure data is not lost even if a storage device fails. However, managing multiple copies increases complexity and can lead to consistency issues if not properly synchronised.</p>
</li>
</ul>
<h3 id="heading-scenario-2">Scenario</h3>
<p>Assume you have an <code>Accounts</code> table and you want to transfer money between two accounts. The transaction should ensure that the transfer is logged, and the changes are committed so that they persist even if the system crashes.</p>
<pre><code class="lang-sql"><span class="hljs-comment">-- AccountID 1 = 500.00</span>
<span class="hljs-comment">-- AccountID 2 = 200.00</span>

<span class="hljs-comment">-- Start the transaction</span>
<span class="hljs-keyword">BEGIN</span> <span class="hljs-keyword">TRANSACTION</span>;

<span class="hljs-comment">-- Assume you want to transfer 100 from AccountID 1 to AccountID 2</span>

<span class="hljs-comment">-- Step 1: Log the transaction</span>
<span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> TransactionsLog (FromAccountID, ToAccountID, Amount) <span class="hljs-keyword">VALUES</span> (<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">100.00</span>);

<span class="hljs-comment">-- Step 2: Update the balance of AccountID 1</span>
<span class="hljs-keyword">UPDATE</span> Accounts
<span class="hljs-keyword">SET</span> Balance = Balance - <span class="hljs-number">100.00</span>
<span class="hljs-keyword">WHERE</span> AccountID = <span class="hljs-number">1</span>;

<span class="hljs-comment">-- Step 3: Update the balance of AccountID 2</span>
<span class="hljs-keyword">UPDATE</span> Accounts
<span class="hljs-keyword">SET</span> Balance = Balance + <span class="hljs-number">100.00</span>
<span class="hljs-keyword">WHERE</span> AccountID = <span class="hljs-number">2</span>;

<span class="hljs-comment">-- Commit the transaction</span>
<span class="hljs-keyword">COMMIT</span>;

<span class="hljs-comment">-- AccountID 1 = 400.00</span>
<span class="hljs-comment">-- AccountID 1 = 300.00</span>
</code></pre>
<h1 id="heading-distributed-transactions">Distributed Transactions</h1>
<p>Distributed transactions involve multiple databases that are geographically distributed across a network. Unlike single-database transactions, distributed transactions require coordination to ensure that all participants either commit or roll back as a single atomic operation. As you can imagine, this adds complexity due to challenges related to resource failures, coordination, and network issues, making it difficult to achieve ACID guarantees.</p>
<h3 id="heading-atomic-commit">Atomic Commit</h3>
<p>The atomic commit problem involves ensuring that a distributed transaction is either committed (completed on all nodes) or aborted (undone on all nodes) even in the presence of failures. All nodes need to reach an agreement on whether or not to commit or abort a transaction. Atomic Commit employs protocols like as <strong>Two-Phase Commit (2PC)</strong>, or <strong>Three-Phase Commit (3PC)</strong> to coordinate the participants.</p>
<h3 id="heading-atomic-commit-vs-consensus">Atomic Commit vs Consensus</h3>
<p>Atomic commit and consensus are related concepts in distributed systems, but they serve different purposes.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td>Atomic Commit</td><td>Consensus</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Purpose</strong></td><td>The goal of atomic commit is to ensure transaction completeness. That is, a distributed transaction is either fully committed or aborted across all participants.</td><td>The goal of consensus is to achieve agreement among distributed participants on a specific value or decision.</td></tr>
<tr>
<td><strong>Process</strong></td><td>Every node must vote whether to commit or abort.</td><td>One or more nodes propose a value.</td></tr>
<tr>
<td><strong>Outcome</strong></td><td>Here, the transaction must commit if all the nodes vote to commit and must abort if any node votes to abort.</td><td>Here, any one of the proposed values is chosen.</td></tr>
<tr>
<td><strong>Failure Handling</strong></td><td>If a participating node crashes, the transaction must abort.</td><td>Consensus algorithms like Raft can tolerate crashes as long as a quorum of nodes is working.</td></tr>
<tr>
<td><strong>Protocols</strong></td><td>Two-phase commit (2PC) and three-phase commit (3PC) are commonly used to achieve atomic commitment.</td><td>Paxos and Raft are commonly used to reach a consensus.</td></tr>
<tr>
<td><strong>Focus</strong></td><td>Atomic Commit focuses on transaction integrity and consistency in distributed transactions.</td><td>Consensus focuses on maintaining consistent state and fault tolerance in distributed systems.</td></tr>
</tbody>
</table>
</div><h1 id="heading-conclusion">Conclusion</h1>
<p>In this article, we covered the basics and differences between transactions and distributed transactions. We explored the ACID properties, the challenges of distributed transactions, and the differences between atomic commit and consensus.</p>
<p>This understanding lays the groundwork for our further exploration into protocols which are used to address the atomic commitment problem, such as Two-Phase Commit (2PC) and Fault-tolerant Two-Phase Commit (FT-2PC), Three-Phase Commit (3PC), Saga, Event Sourcing, Google's Spanner and Percolator.</p>
<p>I hope you are as excited as I am for what's to come!</p>
<h1 id="heading-references">References</h1>
<ol>
<li><p><a target="_blank" href="https://youtu.be/-_rdWB9hN1c?">Distributed Systems Lecture Series by Martin Kleppmann</a></p>
</li>
<li><p><a target="_blank" href="https://dataintensive.net/">Designing Data-Intensive Applications</a></p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Exploring DBMS Storage Structures: Heap- vs Hash- vs Index-Organised Tables]]></title><description><![CDATA[Introduction
Database Management Systems (DBMS) use different storage structures to organise and access data. Three of the most popular storage structures are Heap-Organised Tables, Hash-Organised Tables, and Index-Organised Tables. In this post, we’...]]></description><link>https://chidinma.dev/exploring-dbms-storage-structures-heap-vs-hash-vs-index-organised-tables</link><guid isPermaLink="true">https://chidinma.dev/exploring-dbms-storage-structures-heap-vs-hash-vs-index-organised-tables</guid><category><![CDATA[hashed-file]]></category><category><![CDATA[heap file]]></category><category><![CDATA[database-storage-structure]]></category><category><![CDATA[heap-organised-table]]></category><category><![CDATA[hash-organised-table]]></category><category><![CDATA[index-organised-table]]></category><category><![CDATA[Databases]]></category><category><![CDATA[data structures]]></category><category><![CDATA[database design]]></category><category><![CDATA[DBMS]]></category><category><![CDATA[iot]]></category><dc:creator><![CDATA[Chidinma]]></dc:creator><pubDate>Sun, 28 Apr 2024 22:00:00 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>Database Management Systems (DBMS) use different storage structures to organise and access data. Three of the most popular storage structures are Heap-Organised Tables, Hash-Organised Tables, and Index-Organised Tables. In this post, we’ll see their benefits, use cases and drawbacks.</p>
<h1 id="heading-heap-organised-tables-aka-heap-files">Heap-Organised Tables aka Heap Files</h1>
<p>When I think of Heap-organised tables, I imagine a heap of clothes before doing laundry. Heap-organised tables are typically associated with row-oriented DBMS. In heap-organised tables, records are not arranged in any specific order. Instead, data is inserted into these files sequentially, following the order in which it was received or written (write-order). Hence the nickname “unordered tables” is appropriate for heap-organised tables.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716129253947/9adb3b1f-a61f-477d-a5c4-a32e8558a29c.png" alt="Heap Organised Tables" class="image--center mx-auto" /></p>
<p>The lack of specific ordering in heap files allows for fast data insertion as the DBMS does not need to reorganise or maintain any inherent order. However, the downside lies in data retrieval. As data isn't sorted, any search operation would require a full table scan, which can be time-consuming for large tables.</p>
<p>To make heap files searchable, additional index structures can be created on specific columns of the table. These indexes contain pointers to the locations within the heap file where data records with matching values are stored.</p>
<p>While these indexes improve the search performance of heap files, they come with additional overhead in terms of storage space and maintenance. This is because indexes need to be updated whenever data records are inserted, updated or deleted from the table which can impact the overall performance of the DBMS.</p>
<p>Heap files are great if your operations are heavily focused on data insertion aka write-heavy workloads, and the table size remains relatively small.</p>
<p>Several DBMS, including PostgreSQL and Microsoft SQL Server, default to using heap-organised tables when a table is created without specifying a specific indexing method. In these systems, if no clustered index or primary key is defined during table creation, the table is typically created as a heap-organised table, storing data in the order of insertion.</p>
<h1 id="heading-hash-organised-tables-aka-hashed-files">Hash-Organised Tables aka Hashed Files</h1>
<p>Hash-organised tables use a hash function to determine the storage location of data based on a hash of one or more key columns. Each hash value corresponds to a bucket or slot in the table where data records with that specific hash value are stored. Accessing data in a hashed file typically involves computing the hash value of the search key and then looking up the corresponding bucket to retrieve the data. This approach aims to distribute data evenly across the underlying storage structure, providing fast retrieval for exact match queries aka point lookup queries.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716129596928/81014921-8dc7-4eb1-964b-42cb3984e9f6.png" alt="Hash organised tables" class="image--center mx-auto" /></p>
<p>While hash-organised tables excel at point lookups, they may not perform as well for range scans or queries involving consecutive key values.</p>
<p>In practice, hash functions may lead to collisions, where multiple keys map to the same hash value. Collision resolution techniques, such as chaining or open addressing, address this issue. Chaining involves storing collided records in a linked list or similar structure within the bucket, while open addressing techniques search for an empty slot within the bucket for the collided record.</p>
<p>Data records within buckets can be stored in either append order or sorted by key to optimise lookup speed.</p>
<p>With the <strong>append-order approach</strong>, each new record is appended to the end of the bucket when it arrives. This technique is simple, straightforward and efficient for insertion operations. However, lookup operations in append-ordered buckets may require scanning through all the records in the bucket. As you can guess, this linear search is less efficient with a bucket storing a large number of records.</p>
<p>In contrast, the <strong>sorted-by-key approach</strong> allows for faster lookups using algorithms like binary search. However, maintaining a sorted order within the bucket may introduce additional overhead during insertion and deletion operations as records need to be inserted at the correct position to preserve sorted order.</p>
<p>If a bucket becomes full due to insertions, overflow handling mechanisms are necessary to accommodate additional records. These mechanisms may involve splitting the bucket into multiple buckets or reallocating records to other buckets through a rehashing process.</p>
<p>Some database management systems (DBMS) such as Oracle Berkeley DB and certain configurations of Cassandra, Amazon DynamoDB, and Redis utilise hash-organised tables or hash-based storage mechanisms to optimise for specific workloads. However, these systems often use a combination of data structures tailored to their performance requirements.</p>
<h1 id="heading-index-organised-tables-aka-iots">Index-Organised Tables aka IOTs</h1>
<p>Index-organised tables store the index and data records in a single structure, typically a B-tree index. The primary key of the table is used as the index key, and the data rows are stored in the leaf nodes of the index.</p>
<p>Unlike heap files which can have a separate index structure, IOTs store the records directly within the index structure. This means that the leaf nodes contain the actual data record rather than pointers. This organisation reduces the number of disk seeks and allows for efficient data retrieval and storage.</p>
<p>Accessing data in an index-organised table involves traversing the B-tree index to locate the desired rows.</p>
<p>IOTs are better suited for OLAP (Online Analytical Processing) workloads where queries often involve range scans, ordered retrieval, and aggregation. They are great if your operations involve frequent data retrieval or the table size is large.</p>
<p>While IOTs get a lot of praise, there are some notable drawbacks such as:</p>
<ul>
<li><p><strong>Index Maintenance Overhead</strong>: Because the data and index are stored together in the same structure, any modification to the data (inserts, updates, deletes) may require corresponding updates to the index. This can lead to increased index maintenance overhead, especially for tables with frequent data modifications.</p>
</li>
<li><p><strong>Storage Requirements</strong>: They require more storage space compared to heap-organised tables, especially if the table has a wide primary key or if there are many secondary indexes. Storing data within the index structure can lead to increased storage overhead, particularly for large tables.</p>
</li>
<li><p><strong>Performance for OLTP Workloads</strong>: While IOTs are well-suited for OLAP (Online Analytical Processing) workloads with range scans and ordered retrieval, they may not perform as well for OLTP (Online Transaction Processing) workloads with frequent data modifications and single-row lookups. In some cases, heap-organised tables may offer better performance for OLTP scenarios.</p>
</li>
<li><p><strong>Complexity</strong>: Being more complex to maintain, backup, and recovery operations may require more specialised knowledge and tools.</p>
</li>
</ul>
<p>DBMS whose default table organisation is index-organised tables are relatively rare as most systems default to heap-organised tables or other structures unless explicitly configured otherwise.</p>
<p>Nonetheless, Oracle Database is one of the few DBMS that provides explicit support for IOTs. Oracle is well-known for its robust implementation and support for IOTs.</p>
<h1 id="heading-clustered-index-vs-index-organised-table-iot">Clustered Index vs Index-Organised Table (IOT)</h1>
<p>Clustered Indexes are often confused with Index-Organised Tables (IOTs). While both clustered indexes and Index-Organised Tables involve organising data based on an index, they are distinct concepts.</p>
<p><strong>Clustered Index</strong>:</p>
<ul>
<li><p>Clustered Indexes use a B-tree structure, where the data rows are stored in the same physical order as the index keys. In a table with a clustered index, the index not only stores pointers to the data rows but also determines the physical layout of the data rows themselves. The data is part of the index structure, there is no separate index structure pointing to the data.</p>
</li>
<li><p>Only one clustered index can exist per table because the data rows can be physically ordered in only one way. Typically, a clustered index is created on a primary key column by default. Additional or secondary indexes must be non-clustered, meaning they maintain separate index structures that point to the data rows indirectly.</p>
</li>
<li><p>Many DBMS systems, such as MySQL and its fork MariaDB (using InnoDB storage engine) and SQL Server, use clustered indexes to organise table data for efficient retrieval. In these systems, the primary key often serves as the clustered index by default, providing faster access to data based on the primary key.</p>
</li>
</ul>
<p><strong>Index-Organised Tables (IOTs):</strong></p>
<ul>
<li><p>Index-Organised Tables also use a B-tree structure, integrating both the index and data into a single structure. The data rows are stored directly within the B-tree index, with the leaf nodes containing the actual data records. There is no separate table storage outside the index.</p>
</li>
<li><p>An IOT inherently organises the entire table around the primary key index. The whole table’s data is stored within this integrated index structure, allowing for highly efficient access patterns, especially for range scans and ordered data access. However, this design requires careful consideration of the primary key's design, as it significantly impacts the physical storage of the entire table.</p>
</li>
<li><p>IOTs are specific to databases like Oracle, where they are designed for efficient range scans and ordered data access. The primary key in an IOT dictates the storage order of the data, which can significantly improve query performance for operations involving the primary key or a range of keys.</p>
</li>
</ul>
<h1 id="heading-conclusion">Conclusion</h1>
<p>In this article, we learnt about three popular storage structures used in database systems to organise and access data: Heap-Organised Tables, Hash-Organised Tables, and Index-Organised Tables. Each of these storage structures has its strengths and weaknesses. Below is a summary of the pros and cons associated with each one:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Storage Structure</td><td>Pros</td><td>Cons</td></tr>
</thead>
<tbody>
<tr>
<td>Heap-Organised Tables</td><td>They offer fast data insertion due to the lack of specific ordering. This structure can be made searchable with the addition of indexes, increasing its flexibility and adaptability.</td><td>The major drawback is that data retrieval could be slow since it requires a full table scan. Additionally, the use of indexes introduces overhead in terms of storage space and maintenance.</td></tr>
<tr>
<td>Hash-Organised Tables</td><td>These structures provide fast retrieval for exact match queries, which can be a significant advantage in many use cases. The data is evenly distributed across the storage structure, which can help improve performance.</td><td>However, they can perform poorly for range scans or queries involving consecutive key values. The use of hash functions may also lead to collisions, adding complexity to the data management process.</td></tr>
<tr>
<td>Index-Organised Tables</td><td>These are known for their efficient data retrieval and storage, as well as their ability to reduce the number of disk seeks. This can significantly improve performance, especially in large databases.</td><td>On the downside, they have an increased index maintenance overhead. They also require more storage space, and may not perform well for workloads with frequent data modifications and single-row lookups.</td></tr>
</tbody>
</table>
</div><p>Understanding the characteristics of these storage structures can help in making informed decisions when designing and managing databases. The goal is to leverage the strengths of each storage structure to meet your specific database requirements, while also being aware of their limitations. By doing so, you can design and manage your databases more effectively, ensuring optimal performance and efficiency.</p>
<h1 id="heading-glossary">Glossary</h1>
<ul>
<li><strong>Point queries</strong> retrieve individual records from a DB based on certain criteria. They are simple and efficient since they access a specific record without scanning large portions of the DB. For example, retrieving a user’s information based on their unique user ID.</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">users</span> <span class="hljs-keyword">WHERE</span> user_id = <span class="hljs-number">100</span>;
</code></pre>
<ul>
<li><strong>Range scans</strong> on the other hand involve querying a range of records within a specified range of values. Range scans return multiple records that satisfy the conditions. For example, retrieving data based on date ranges.</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> sales_transactions
<span class="hljs-keyword">WHERE</span> transaction_date <span class="hljs-keyword">BETWEEN</span> <span class="hljs-string">'2022-01-01'</span> <span class="hljs-keyword">AND</span> <span class="hljs-string">'2022-03-31'</span>;
</code></pre>
<h1 id="heading-references-and-additional-reading">References and Additional Reading</h1>
<ol>
<li><p><a target="_blank" href="https://www.databass.dev/">Database Internals Book</a></p>
</li>
<li><p><a target="_blank" href="https://nenadnoveljic.com/blog/index-organized-table-vs-clustered-index/">Index-Organised Table VS. Clustered index</a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/pulse/heap-vs-index-organized-table-pranav-pandey">Heap vs Index organized table</a></p>
</li>
<li><p><a target="_blank" href="https://www.javatpoint.com/dbms-heap-file-organization">Javatpoint.com</a></p>
</li>
<li><p><a target="_blank" href="https://www.geeksforgeeks.org/file-organization-in-dbms-set-1/">GeeksforGeeks.com</a></p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Introduction to String Matching Algorithms]]></title><description><![CDATA[String matching is a fundamental problem in computer science. Simply put, string matching involves searching for patterns within a text. String-matching algorithms are used to find occurrences of a specified pattern (or substring) within a larger tex...]]></description><link>https://chidinma.dev/introduction-to-string-matching-algorithms</link><guid isPermaLink="true">https://chidinma.dev/introduction-to-string-matching-algorithms</guid><category><![CDATA[string matching]]></category><category><![CDATA[Rabin-Karp]]></category><category><![CDATA[kmp]]></category><category><![CDATA[kmp_algorithm]]></category><category><![CDATA[algorithms]]></category><category><![CDATA[advanced algorithms]]></category><dc:creator><![CDATA[Chidinma]]></dc:creator><pubDate>Sat, 30 Mar 2024 23:20:12 GMT</pubDate><content:encoded><![CDATA[<p>String matching is a fundamental problem in computer science. Simply put, string matching involves searching for patterns within a text. String-matching algorithms are used to find occurrences of a specified pattern (or substring) within a larger text (or string). These algorithms can be found in fields such as:</p>
<ul>
<li><p><strong>Information retrieval</strong>: String matching algorithms are used in search engines, databases, and information retrieval systems to efficiently locate relevant documents, records, or data based on user queries or patterns.</p>
</li>
<li><p><strong>Text Processing</strong>: Many text-processing tasks, such as parsing, syntax highlighting, spell checking, and natural language processing, rely on string-matching algorithms to identify and manipulate specific patterns or language constructs within text.</p>
</li>
<li><p><strong>Bioinformatics</strong>: String matching algorithms are used to analyse biological sequences (e.g., DNA, RNA, protein sequences) for sequence alignment, similarity search, and gene pattern identification.</p>
</li>
<li><p><strong>Data mining and Pattern Recognition</strong>: String matching algorithms are used to identify trends, patterns, or anomalies within large datasets, enabling insights and decision-making.</p>
</li>
<li><p><strong>Network Security</strong>: String matching plays a crucial role in network security, intrusion detection, and firewall systems by inspecting network traffic, identifying malicious patterns (e.g., virus signatures, attack patterns), and enforcing security policies.</p>
</li>
</ul>
<p>Understanding how string matching works is essential for anyone working in a field that requires manipulating and analysing large amounts of data.</p>
<p>Now that we know what string-matching is and its significance to computer science out of the way, welcome to the first instalment in our series on string-matching algorithms! Over the upcoming articles, we'll delve into a variety of algorithms each with its advantages and drawbacks.</p>
<p>This article marks the beginning of our exploration into various types of string-matching algorithms. To start, let's dive into the naive string-matching algorithm.</p>
<h2 id="heading-naive-string-matching-algorithm">Naive String Matching algorithm</h2>
<p>The naive string-matching algorithm is the most simple and intuitive string-matching algorithm. No wonder we are starting our exciting ride into the wonders of string-matching here.</p>
<p>This straightforward approach checks for a match with the target string at every possible position in the text. Simple, right? Right.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💭</div>
<div data-node-type="callout-text"><strong>Question</strong></div>
</div>

<p>Find the Index of the First Occurrence in a String.<br />Given two strings, text and pattern, return the index of the first occurrence of pattern in the text, or -1 if the pattern is not part of the text.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">find_first_index</span>(<span class="hljs-params">text: str, pattern: str</span>) -&gt; int:</span>
    text_len, pattern_len = len(text), len(pattern)

    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(text_len - pattern_len + <span class="hljs-number">1</span>):
        <span class="hljs-keyword">for</span> j <span class="hljs-keyword">in</span> range(pattern_len):

            <span class="hljs-keyword">if</span> pattern[j] != text[i + j]:
                <span class="hljs-keyword">break</span>

            <span class="hljs-keyword">if</span> j == pattern_len - <span class="hljs-number">1</span>:
                <span class="hljs-keyword">return</span> i

    <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">✅</div>
<div data-node-type="callout-text">Example 1</div>
</div>

<p>text = ‘ABCBADC’<br />pattern = ‘BAD’</p>
<p>index = find_first_index(text, pattern) # 3</p>
<p>Pattern found at the 3rd index</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">✅</div>
<div data-node-type="callout-text">Example 2</div>
</div>

<p>text = 'mississippi’<br />pattern = ‘issip’</p>
<p>index = find_first_index(text, pattern) # 4</p>
<p>Pattern found at the 4th index</p>
<h2 id="heading-time-and-space-complexity">Time and Space complexity</h2>
<p>In the worst-case scenario, <strong>the time complexity of the naive string-matching algorithm is O((m - n + 1) * n).</strong></p>
<p>Where <code>m</code> is the length of the text and <code>n</code> is the length of the pattern.</p>
<p>The worst-case scenario occurs when the pattern is not found in the text or is found at the end of the text. In this case, the algorithm will make comparisons for each position of the text with the pattern, resulting in a quadratic time complexity.</p>
<p><strong>The space complexity is O(1).</strong></p>
<p>The naive string-matching algorithm operates in place, meaning it does not require any additional space proportional to the input size. It only uses a constant amount of space for variables and temporary storage.</p>
<blockquote>
<p>Despite its simplicity, checking for a match this way is NOT efficient for large text and pattern sizes.</p>
</blockquote>
<h1 id="heading-more-efficient-string-matching-algorithms">More Efficient String Matching Algorithms</h1>
<p>There are more efficient string-matching algorithms such as:</p>
<ol>
<li><p>Exact String-matching algorithms: such as</p>
<ul>
<li><p>Rabin-Karp Algorithm: which efficiently utilises hashing for pattern matching.</p>
</li>
<li><p>Knuth-Morris-Pratt (KMP) Algorithm: which employs a failure function to avoid unnecessary comparisons.</p>
</li>
<li><p>Boyer-Moore Algorithm: which uses mismatched character information to skip unnecessary comparisons.</p>
</li>
</ul>
</li>
<li><p>Approximate String Matching Algorithms: such as Levenshtein distance.</p>
</li>
<li><p>Advanced String Matching Algorithms such as Aho-Corasick Algorithm, Suffix Trees and Suffix Arrays.</p>
</li>
</ol>
<h1 id="heading-conclusion">Conclusion</h1>
<p>In this article, we covered the basics of string matching algorithms, their importance, and where they are used, such as in information retrieval, text processing, bioinformatics, data mining, pattern recognition, and network security.</p>
<p>We learned that string matching involves searching for a pattern within a larger text, and understanding this is essential for anyone working with large amounts of data.</p>
<p>We explored the naive string-matching algorithm, its simplicity, and how it operates, as well as its time and space complexity.</p>
<p>We also briefly mentioned more efficient string-matching algorithms like the Rabin-Karp, Knuth-Morris-Pratt, Boyer-Moore, Levenshtein distance, Aho-Corasick, Suffix Trees and Suffix Arrays.</p>
<p>This understanding lays the groundwork for our further exploration into more complex string-matching algorithms.</p>
<h1 id="heading-references">References</h1>
<ul>
<li><p><a target="_blank" href="https://en.wikipedia.org/wiki/String-searching_algorithm">https://en.wikipedia.org/wiki/String-searching_algorithm</a></p>
</li>
<li><p><a target="_blank" href="https://www.geeksforgeeks.org/introduction-to-pattern-searching-data-structure-and-algorithm-tutorial">https://www.geeksforgeeks.org/introduction-to-pattern-searching-data-structure-and-algorithm-tutorial</a></p>
</li>
</ul>
]]></content:encoded></item></channel></rss>