<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lukas Niessen</title>
    <description>The latest articles on DEV Community by Lukas Niessen (@lukasniessen).</description>
    <link>https://dev.to/lukasniessen</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3134411%2F65d944bb-0c34-4e4f-b114-4fc5ea7679d7.jpg</url>
      <title>DEV Community: Lukas Niessen</title>
      <link>https://dev.to/lukasniessen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lukasniessen"/>
    <language>en</language>
    <item>
      <title>Idempotence in System Design: Full example</title>
      <dc:creator>Lukas Niessen</dc:creator>
      <pubDate>Mon, 20 Oct 2025 14:55:00 +0000</pubDate>
      <link>https://dev.to/lukasniessen/idempotence-in-system-design-full-example-2acb</link>
      <guid>https://dev.to/lukasniessen/idempotence-in-system-design-full-example-2acb</guid>
      <description>&lt;h1&gt;
  
  
  Idempotency in System Design: Full example
&lt;/h1&gt;

&lt;p&gt;Idempotency is a concept frequently mentioned in system design. I will explain what it means in simple terms, briefly address common misunderstandings, and finish with a full example. &lt;/p&gt;

&lt;h2&gt;
  
  
  What Is It?
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Something&lt;/em&gt; is idempotent if doing it once or multiple times gives the same outcome.&lt;/p&gt;

&lt;p&gt;In other words, if I do that &lt;em&gt;something&lt;/em&gt; once, I get the same result as when I do it 2 times or 3 times or 10 times. Let's look at the standard example: we have an on and off button. Pressing them is an idempotent operation. If you press &lt;em&gt;on&lt;/em&gt; once, the machine is on. If you then press it again, and again and again, nothing changes. The machine stays on. Same for the off button. &lt;/p&gt;

&lt;p&gt;Here's an example from programming:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hide_my_button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show_my_button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is clearly idempotent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;toggle_my_button_visibility&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show_my_button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show_my_button&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is of course &lt;strong&gt;not&lt;/strong&gt; idempotent.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's not about the return value!
&lt;/h2&gt;

&lt;p&gt;This is a common misunderstanding. One could implement the hide function from above like this as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hide_my_button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;has_something_changed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show_my_button&lt;/span&gt;
  &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show_my_button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;has_something_changed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we return whether something was changed or not. If we call this multiple times, the returned value might differ! But it's still idempotent because idempotency is about the effect produced on the &lt;em&gt;"state"&lt;/em&gt; or &lt;em&gt;"effect"&lt;/em&gt; and not about the response status code received by the client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotent vs Pure
&lt;/h2&gt;

&lt;p&gt;Although &lt;em&gt;pure&lt;/em&gt; is not a topic of this article, I still want to address this quickly because it's a common source of confusion.&lt;/p&gt;

&lt;p&gt;A function or operation is &lt;em&gt;pure&lt;/em&gt; if, given the same input, it always produces the same output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;my_number&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a pure function. &lt;code&gt;square(3)&lt;/code&gt; will always be the same number.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;square_with_randomness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_number&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;strong&gt;not&lt;/strong&gt; a pure function. &lt;code&gt;square(3)&lt;/code&gt; will almost always be a different outcome since we multiply it with a random number between &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt;. Likewise, if we would multiply it with some global variable or some class variable, it would no longer be pure. The global variable can change and then our outcome would be different.&lt;/p&gt;

&lt;p&gt;Okay, let's look at &lt;code&gt;def square(my_number)&lt;/code&gt; again. It's pure. But is it idempotent? Of course not. Apply it once to &lt;code&gt;2&lt;/code&gt; and we get &lt;code&gt;4&lt;/code&gt;. Apply it again and we get &lt;code&gt;16&lt;/code&gt;. So a different number!&lt;/p&gt;

&lt;p&gt;It's also easy to find an example of an idempotent operation that is not pure. So the two concepts are totally different things!&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotence in System Design
&lt;/h2&gt;

&lt;p&gt;So why is it such an important concept in system design? There are many reasons and we will discuss the most common ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Message Processing
&lt;/h3&gt;

&lt;p&gt;Suppose we use event-driven design in our system. Concretely, we have a message queue and a service consuming its messages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/pic1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/pic1.png" title="Diagram 1" alt="diagram1" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problem is this. When &lt;code&gt;Service B&lt;/code&gt; consumes a message, let's say the message containing &lt;code&gt;Event 3&lt;/code&gt;, it processes it, and then writes to our DB. Let's keep it super simple, suppose &lt;code&gt;Service B&lt;/code&gt; calculates some complex formula for each event and writes the result to our DB. Now it's very important that nothing here gets lost ever. We have very important data! &lt;/p&gt;

&lt;p&gt;But if &lt;code&gt;Service B&lt;/code&gt; crashes during the calculation, or there is a network partition between &lt;code&gt;Service B&lt;/code&gt; and the DB, or something else happens, then the message and the event are lost forever. Terrible.&lt;/p&gt;

&lt;p&gt;The solution is simple: instead of removing the message immediately from the queue, we &lt;strong&gt;wait for &lt;code&gt;Service B&lt;/code&gt; to be finished&lt;/strong&gt;, which includes writing to the DB, and &lt;strong&gt;then&lt;/strong&gt; remove the message.&lt;/p&gt;

&lt;p&gt;But this introduces a new problem. It's possible that the same message is read twice. For example, &lt;code&gt;Service B&lt;/code&gt; performs the calculation and writes to the DB, but then something happens. It crashes for example. So before the message is removed from the queue, the service has crashed. What happens? The service restarts, and once it's up again and running, it will continue consuming messages. And it starts with exactly that last message. So that message gets consumed twice!&lt;/p&gt;

&lt;p&gt;How do we solve this? We can't really directly. System design is always about trade-offs: &lt;strong&gt;Either we might lose messages or we might consume the message more than once.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But that's not so problematic! If we design the operation of &lt;code&gt;Service B&lt;/code&gt; to be idempotent, then nothing happens. The service will consume the message a second time, but it doesn't matter because the operation is idempotent. So the outcome is still the same.&lt;/p&gt;

&lt;p&gt;The only downside is a little bit of extra complexity (you need to come up with a way to make the operation idempotent) and a little bit of compute resources (potentially doing the same thing more than once unnecessarily). But usually, and definitely in our case, it's better than losing messages!&lt;/p&gt;

&lt;h4&gt;
  
  
  Pitfalls
&lt;/h4&gt;

&lt;p&gt;There are several things to be careful with here. One thing that can happen is an infinite loop (a catastrophic failover). If you have an &lt;em&gt;"ill"&lt;/em&gt; message, for example of an invalid format, that makes your consuming service crash (&lt;code&gt;Service B&lt;/code&gt;), it will stay on the queue. Meaning, &lt;code&gt;Service B&lt;/code&gt; restarts just to consume the same ill message and crash again. And over and over. Even if your system doesn't have such issues, they will sooner or later arise, so you really should make use of a so called &lt;em&gt;dead-letter queue&lt;/em&gt; (or &lt;em&gt;message hospital&lt;/em&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Uses of Idempotency
&lt;/h2&gt;

&lt;p&gt;We've talked about message processing, but idempotency shows up everywhere in system design. Let me walk you through the other most common ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  APIs
&lt;/h3&gt;

&lt;p&gt;If you're building REST APIs, you're already dealing with idempotency whether you realize it or not. The HTTP protocol actually defines which methods should be idempotent:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GET&lt;/strong&gt; requests don't change anything on the server, so they're naturally idempotent. Call them 100 times, same result every time. You can refresh a webpage as many times as you want without worrying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PUT&lt;/strong&gt; requests should completely replace a resource. If you PUT the same data twice, you get the same outcome. Think of it like overwriting a file - doing it twice doesn't change anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DELETE&lt;/strong&gt; requests should delete a resource. Delete something that's already gone? It's still gone. No problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;POST&lt;/strong&gt; requests are usually &lt;strong&gt;not&lt;/strong&gt; idempotent by design. Each POST typically &lt;strong&gt;creates&lt;/strong&gt; something. But you can make them idempotent with &lt;strong&gt;idempotency keys&lt;/strong&gt;. Here's how it works: you send a unique ID with your request (often in a header), and the server remembers "I already processed this ID, so I'll just return the same result instead of doing the work again".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;idempotency_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Did we already process this exact request?
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;idempotency_key&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;already_processed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;get_cached_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Nope, create the user
&lt;/span&gt;    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Cache the response for next time
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;cache_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Databases
&lt;/h3&gt;

&lt;p&gt;Database operations love being idempotent too. Here are the most common patterns:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPSERT operations&lt;/strong&gt; (INSERT or UPDATE if exists) are naturally idempotent. Run an upsert 10 times with the same data, and you get the same result every time. The record either gets created once or updated to the same values multiple times.&lt;/p&gt;

&lt;h3&gt;
  
  
  Distributed Systems
&lt;/h3&gt;

&lt;p&gt;In distributed systems, things fail constantly. Networks partition, services crash, hard drives die, and yes, occasionally cats do walk over keyboards. So we retry operations all the time. But retries only work safely if your operations are idempotent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Example: Order Processing System
&lt;/h2&gt;

&lt;p&gt;Alright, let's put this all together with a concrete example that matches the system in your diagram. We have a simple order processing pipeline: orders come from a web app, get validated by an order service, go into a queue, and then get processed by an order processor service that writes to a database.&lt;/p&gt;

&lt;h3&gt;
  
  
  What We're Building
&lt;/h3&gt;

&lt;p&gt;The system is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Web App&lt;/strong&gt; sends HTTP POST requests with order data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway&lt;/strong&gt; handles routing, authentication, and rate limiting
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order Service&lt;/strong&gt; validates the order and publishes it to the queue&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon SQS&lt;/strong&gt; holds the order messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order Processor Service&lt;/strong&gt; consumes messages and writes to the database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orders DB&lt;/strong&gt; stores all our order data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dead-Letter Queue&lt;/strong&gt; catches any poison messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notification Service&lt;/strong&gt; sends confirmations to customers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key here is that the Order Processor Service needs to be idempotent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making the Order Processor Service Idempotent
&lt;/h3&gt;

&lt;p&gt;The Order Processor Service consumes messages from SQS and does the actual business logic.&lt;/p&gt;

&lt;p&gt;When we process a message, so an order event, we want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check if it was processed already&lt;/li&gt;
&lt;li&gt;If not, insert it into our OrdersDB&lt;/li&gt;
&lt;li&gt;If not, tell NotificationService to send a notification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is idempotent because we check if it was processed already. That could be for example by doing a &lt;code&gt;SELECT&lt;/code&gt; in the OrderDB and only inserting if it's not there yet. Something similar can be done for the NotificationService, or inside the NotificationService with its own DB. &lt;/p&gt;

&lt;p&gt;However, note that we need to deal with concurrency issues. What if we have two different instances of OrderProcessService processing the same message? And they both execute the &lt;code&gt;SELECT&lt;/code&gt; at the same time. We would process the message twice, not good. So we need to wrap this logic into a transaction.&lt;/p&gt;

&lt;p&gt;We would end up something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/diag2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/diag2.jpg" title="Diagram 2" alt="diag2" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another note: We should to make the system actually fully resilient, put a queue in between OrderProcessService and NotificationService as well and do a similar thing.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>systemdesign</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Technical Sales &amp; Presales 101: The very basics</title>
      <dc:creator>Lukas Niessen</dc:creator>
      <pubDate>Thu, 21 Aug 2025 18:53:09 +0000</pubDate>
      <link>https://dev.to/lukasniessen/technical-sales-presales-101-the-very-basics-49nh</link>
      <guid>https://dev.to/lukasniessen/technical-sales-presales-101-the-very-basics-49nh</guid>
      <description>&lt;h1&gt;
  
  
  Technical Sales &amp;amp; Presales 101: The very basics
&lt;/h1&gt;

&lt;p&gt;This article is mainly aimed at developers looking to switch into technical sales. So I cover the very basics of this topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lead
&lt;/h2&gt;

&lt;p&gt;A lead is just a potential customer. This can be someone that signed up for a demo, someone in your contacts who you think might be interested in your product, someone who signed up for a free trial etc. There are however different types of leads and I will introduce them now. &lt;/p&gt;

&lt;p&gt;To avoid confusion, a lead typically refers to a person. That person of course it usually associated to a company, and that company will hopefully become a customer one day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Marketing Qualified Lead (MQL)
&lt;/h3&gt;

&lt;p&gt;A lead that meets certain marketing criteria (right job title, company size, industry, engagement with marketing content). Marketing might say: &lt;em&gt;"This person looks like our ICP (ideal customer profile)"&lt;/em&gt;. This means, that person is a MQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sales Accepted Lead (SAL)
&lt;/h3&gt;

&lt;p&gt;This is a lead where sales agrees it's worth working on. So the marketing team has a lead and the sales team &lt;em&gt;"agrees it's a good lead&lt;/em&gt;". The lead then is considered a SAL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sales Qualified Lead (SQL)
&lt;/h3&gt;

&lt;p&gt;This is the stage we desire. This is the stage we want in order to continue with actually trying to make this lead a customer. So what a SQL is, is really just a lead that has shown interest in becoming a customer and marketing and sales agree they're a good fit.&lt;/p&gt;

&lt;p&gt;Often, &lt;em&gt;BANT&lt;/em&gt; is used to see whether a lead is a good fit. BANT = Budget, Authority, Need, and Timeline. A SQL is often also called a &lt;em&gt;prospect&lt;/em&gt;. BANT is just one framework though, &lt;em&gt;MEDDICC&lt;/em&gt; is another important one. Some teams also use no framework at all.&lt;/p&gt;

&lt;p&gt;SQLs are so important because they are the group of leads that are most likely to be converted into customers. &lt;/p&gt;

&lt;p&gt;For example, imagine you're selling cloud infrastructure services. A SQL might be a CTO from a growing startup who has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Budget:&lt;/strong&gt; $50k+ annual cloud budget &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authority:&lt;/strong&gt; Decision-making power for technical purchases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Need:&lt;/strong&gt; Their current hosting can't handle traffic growth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeline:&lt;/strong&gt; They need to migrate within 6 months due to a major product launch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We generally want to know as much as possible about our leads. This helps to identify why they need our product or service. This allows us for a tailored pitch and tailored language. And so on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Process
&lt;/h2&gt;

&lt;p&gt;What we want is to find many potential customers, also called &lt;em&gt;generating leads&lt;/em&gt; and at some point convert them into customers. So we want: lead ➜ SQL ➜ customer. This is the process. Here some more details.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Generating leads:&lt;/strong&gt; there are many ways to generate leads and this is a big topic on its own. Some are networking, asking for referrals, or internet marketing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. BANT:&lt;/strong&gt; So now that we have leads, we want to see whether they are qualified, so we would invest more time in them. Again: Budget, Authority, Need, and Timeline. So we determine if the lead has the financial resources, decision-making authority, genuine requirement for the product or service, and a specific timeframe for purchase. If yes, then we consider this lead a strong candidate for further qualification. This assessment is typically done by sales representatives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Lead Scoring:&lt;/strong&gt; As the next step, we &lt;em&gt;score our leads&lt;/em&gt;. That is, we assign them numerical values that represent how likely it is to convert them into customers. This can be based on many things, such as company size, engagement level and job title.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Lead Nurturing:&lt;/strong&gt; By lead nurturing we mean &lt;em&gt;taking care of a lead&lt;/em&gt;. The goal is to build trust and spark interest. This means providing info about the product or service and addressing their pain points. This may be achieved through personalised email campaigns, case studies, content marketing, and webinars. Important, we use our lead scoring to decide how much time we invest into lead nurturing for each lead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Scheduling a Meeting or Call:&lt;/strong&gt; Once a lead has shown strong interest in the product or service, we schedule a meeting or a call. Here we will dive deep into the lead's requirements, understand their challenges and pain points, and present tailored solutions. This is often called a &lt;em&gt;discovery call&lt;/em&gt; and might involve both the AE and a solution engineer if technical questions are expected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Closing the Deal:&lt;/strong&gt; This is the final step of this process. When the lead shows a strong interest in the service or product and you've had a meeting or multiple already, it might be time for closing the deal. This includes making a deal proposal, which includes a summary of the customer's needs, a detailed explanation of the proposed solution, why and how that's good for the customer, pricing, terms and conditions and more. But it also includes negotiating aspects of the proposal. For complex technical sales, this might also include technical proofs of concept or pilot implementations. &lt;/p&gt;

&lt;p&gt;This process is called sales pipeline. If we would outline the same process but write everything from the customer's perspective instead and notice that the amount of leads decreases with every step, it would be called sales funnel. However, these two terms are sometimes used interchangeably.&lt;/p&gt;

&lt;p&gt;Note that we also often say &lt;em&gt;the pipeline&lt;/em&gt; and mean the above process together with all existing leads. So the &lt;em&gt;state&lt;/em&gt; your entire sales cylce is in right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  People involved
&lt;/h2&gt;

&lt;p&gt;Let's clarify who is involved in this process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Account Executive (AE)
&lt;/h3&gt;

&lt;p&gt;The account executive, also called sales representative or just sales rep, is the person who acts as the primary point of contact and &lt;em&gt;owns&lt;/em&gt; a particular lead or potential deal. This means, he is the main face of your company to this lead. He is responsible for building the relationship and understanding the customer's needs.&lt;/p&gt;

&lt;p&gt;For example, if you're selling enterprise software, the AE might be responsible for 20-30 active opportunities, each representing potential deals worth $50k-$500k. They spend their time on calls understanding business requirements, presenting value propositions, and navigating the customer's procurement process.&lt;/p&gt;

&lt;p&gt;However, he is not working alone of course.&lt;/p&gt;

&lt;p&gt;The entire process can only start if we have leads. The lead generation is typically done by the marketing team (doing ads, social media marketing, online content etc).&lt;/p&gt;

&lt;p&gt;Next, we distinguish between &lt;em&gt;pre sales&lt;/em&gt; and &lt;em&gt;post sales&lt;/em&gt;. Not hard to guess, but presales is all the activities and support that occur before a sale closes. That includes customer research, prospecting, discovery (including technical discovery) and more. Post sales is everything after the deal was closed, so for example a good onboarding and general &lt;em&gt;customer success&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Just as a note, developers, software architects, designers and so on, are not part of this process. At least not by the standard business lingo. Of course, the developers start working after the deal was closed, but when we say &lt;em&gt;"post sales"&lt;/em&gt;, we're not talking about that. We're talking about things like customer suport or account management.&lt;/p&gt;

&lt;p&gt;Now when we talk about &lt;em&gt;someone in presales&lt;/em&gt;, often we mean someone with technical expertise. That is often a &lt;em&gt;solution engineer&lt;/em&gt; or a &lt;em&gt;solution architect&lt;/em&gt; and their role typically is to help with technical discovery, answer technical questions that might come up (AEs don't know the answer normally), help architect and design the proposed solution and take part in the presentation of it, in the pitch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not everyone is a Prospect
&lt;/h2&gt;

&lt;p&gt;I will not dive deep here, but I want to mention, that it's important to understand that not every lead is a prospect. It's &lt;strong&gt;very important&lt;/strong&gt; to narrow down in the sales cycle. There is a reason we have different terms (Lead, MQL, SQL). If you don't narrow it down and do good lead scoring, you will waste resources massively. Don't treat everyone like SQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution Engineers &amp;amp; Architects in Presales
&lt;/h2&gt;

&lt;p&gt;For developers considering a transition into sales, the solution engineer or solution architect role is often the natural entry point. These roles bridge the gap between technical expertise and business value. Let me break down what this actually looks like in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Solution Engineers Do
&lt;/h3&gt;

&lt;p&gt;A solution engineer (SE) is essentially the technical wing of the sales team. While the AE focuses on relationship building and understanding business needs, the SE handles all technical aspects of the sale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical Discovery:&lt;/strong&gt; This means understanding the customer's current technical environment. For example, if you're selling a cloud platform, you'd need to understand their current infrastructure, what databases they use, their security requirements, and their deployment processes. You're not just asking &lt;em&gt;"what technology do you use?"&lt;/em&gt; but rather &lt;em&gt;"how does your current system handle peak traffic?"&lt;/em&gt; or &lt;em&gt;"what's your disaster recovery setup?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution Design:&lt;/strong&gt; Based on the discovery, you design a solution that fits their specific needs. This isn't about presenting a generic demo, but rather showing exactly how your product would integrate into their environment. For instance, if they're a retail company with seasonal traffic spikes, you'd design a solution that shows auto-scaling capabilities specifically for their Black Friday traffic patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical Demos:&lt;/strong&gt; You'll give live demonstrations of the product, often customized to their use case. This might mean setting up a demo environment that mirrors their data structure or showing how your API would integrate with their existing systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proof of Concepts (POCs):&lt;/strong&gt; Sometimes customers want to test your solution with their actual data or use cases. You'd help set up and run these technical evaluations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Examples of Solution Engineer Work
&lt;/h3&gt;

&lt;p&gt;Let's say you're selling a data analytics platform to a logistics company:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discovery:&lt;/strong&gt; You'd learn they track 50,000 shipments daily, use Oracle databases, have compliance requirements, and their current reporting takes 3 hours to generate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solution Design:&lt;/strong&gt; You'd design a solution showing real-time dashboards, automated compliance reporting, and integration with their Oracle systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo:&lt;/strong&gt; You'd use their actual shipping data structure to show how reports that currently take 3 hours could be generated in real-time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POC:&lt;/strong&gt; They might want to test with their actual data for 30 days to see the performance improvements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Or if you're selling cybersecurity software to a financial services company:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discovery:&lt;/strong&gt; Understanding their current security stack, compliance requirements (like PCI DSS), incident response procedures, and integration needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solution Design:&lt;/strong&gt; Showing how your solution fits into their existing security infrastructure without disrupting operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo:&lt;/strong&gt; Demonstrating threat detection using scenarios relevant to financial services, like detecting suspicious transaction patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Solution Architect vs Solution Engineer
&lt;/h3&gt;

&lt;p&gt;The terms are often used interchangeably, but there can be subtle differences:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution Architect&lt;/strong&gt; typically implies more strategic, high-level design work. They might work on larger, more complex deals and focus on architectural patterns and long-term technical strategy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution Engineer&lt;/strong&gt; often handles more hands-on technical work, demos, POCs, and day-to-day technical customer interactions.&lt;/p&gt;

&lt;p&gt;In smaller companies, one person might do both roles. In larger companies, you might have senior solution architects who design complex solutions and junior solution engineers who execute demos and handle technical questions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Role Works for Developers
&lt;/h3&gt;

&lt;p&gt;This role is attractive for developers because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You use your technical skills daily&lt;/strong&gt; - understanding APIs, databases, cloud architecture, security patterns, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You learn business context&lt;/strong&gt; - seeing how technology solves real business problems, not just technical challenges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct customer interaction&lt;/strong&gt; - you get immediate feedback on how your technical solutions impact real users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Higher compensation&lt;/strong&gt; - presales roles typically pay more than pure development roles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Career progression&lt;/strong&gt; - you can move into sales leadership, product management, or customer success roles.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key difference from development is that instead of building solutions, you're designing and demonstrating them to solve specific customer problems. Your success is measured not by code quality or features shipped, but by whether customers understand and buy the technical solution you've presented.&lt;/p&gt;

&lt;p&gt;But ultimately, it's a matter of whether you like to sell or not. Personally, I think sales is a super interesting field to work in as, when you think about it, everything in life is basically sales. So becoming good at it is not just a career win :)&lt;/p&gt;

</description>
      <category>sales</category>
      <category>presales</category>
      <category>programming</category>
      <category>consulting</category>
    </item>
    <item>
      <title>Event Sourcing, CQRS and Micro Services: Real FinTech Example from my Consulting Career</title>
      <dc:creator>Lukas Niessen</dc:creator>
      <pubDate>Mon, 30 Jun 2025 22:18:14 +0000</pubDate>
      <link>https://dev.to/lukasniessen/event-sourcing-cqrs-and-micro-services-real-fintech-example-from-my-consulting-career-1j9b</link>
      <guid>https://dev.to/lukasniessen/event-sourcing-cqrs-and-micro-services-real-fintech-example-from-my-consulting-career-1j9b</guid>
      <description>&lt;p&gt;This is a detailed breakdown of a FinTech project from my consulting career. I'm writing this because I'm convinced that this was a great architecture choice and there aren't many examples of event sourcing and CQRS in the internet where it actually makes sense. You are very welcome to share your thoughts and whether you agree about this design choice or not :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Description
&lt;/h2&gt;

&lt;p&gt;The client was a medium sized fintech company that has in-house developed a real time trading platform that was launched as a beta test version. The functionality included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real time stock info&lt;/li&gt;
&lt;li&gt;Portfolio management&lt;/li&gt;
&lt;li&gt;Real time transaction tracking&lt;/li&gt;
&lt;li&gt;Report generation&lt;/li&gt;
&lt;li&gt;Account with a little social media functionality (making posts, liking and commenting)&lt;/li&gt;
&lt;li&gt;Mobile device notifications&lt;/li&gt;
&lt;li&gt;and more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Their app was an MVP. It had a monolithic Spring boot backend and a simple React based web UI, everything hosted on Azure.&lt;/p&gt;

&lt;p&gt;They hired us because of two main reasons: their MVP was not auditable and thus not compliant with financial regulations and also not scalable (high usage and fault tolerance).&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Team
&lt;/h2&gt;

&lt;p&gt;We worked with the customer, not alone. Our team were about 10 people, experienced back end or full stack developers and me as a software architect. The client had about 20 developers, ranging from front end, back end to database experts and more. My role was to lead the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Design Decision
&lt;/h2&gt;

&lt;p&gt;As said, the main issues to solve were auditability (compliance) and scalability (including performance and fault tolerance). I will start with an overview of the design including a super short repetition of what each technology is and later dive into detail in the next session, including discussing the trade offs and alternative solutions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auditability
&lt;/h3&gt;

&lt;p&gt;Our customer must by law always know past states. For example, customer A had exactly $901 on their account 2 months ago at 1:30 pm. This was not possible with the existing system so we needed to tackle it. I proposed to use event sourcing. Here is a very brief explanation of event sourcing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Event sourcing = Save events, not state&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So instead of having a state we update, we save events. We use these events to create the state when we need it. Consider this simple example:&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Old Approach&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------+
| Table: Account_Balance               |
+--------------------------------------+
| Account_ID | Balance | Last_Updated  |
+--------------------------------------+
| Customer_A | $0      | 2025-04-29    | &amp;lt;- Initial state
+--------------------------------------+
| Customer_A | $5      | 2025-04-29    | &amp;lt;- After receiving $5 (overwrites $0)
+--------------------------------------+
| Customer_A | $12     | 2025-04-29    | &amp;lt;- After receiving $7 (overwrites $5)
+--------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Problem: Past states (e.g., $5 at 1:30 PM) are lost unless separately logged.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Event Sourcing&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------------------------------------------------------------------+
| Table: Account_Events                                             |
+-------------------------------------------------------------------+
| Event_ID | Account_ID | Event_Type | Amount | Timestamp           |
+-------------------------------------------------------------------+
| 1        | Customer_A | Deposit    | $5     | 2025-04-29 13:30:00 | &amp;lt;- Event: Received $5
+-------------------------------------------------------------------+
| 2        | Customer_A | Deposit    | $7     | 2025-04-29 13:31:00 | &amp;lt;- Event: Received $7
+-------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reconstructing Balance at 2025-04-29 13:30:00:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sum events up to timestamp: $5 = $5&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reconstructing Balance at 2025-04-29 13:31:00:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sum events up to timestamp: $5 + $7 = $12&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reconstructing Balance 2 months ago:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sum all relevant events &amp;lt;= timestamp&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;This is event sourcing in a nutshell. For a more comprehensive explanation, please have a look &lt;a href="https://martinfowler.com/eaaDev/EventSourcing.html" rel="noopener noreferrer"&gt;here&lt;/a&gt; for example.&lt;/p&gt;

&lt;p&gt;This is the right choice here because this gives us total control and transparency. When we want to know how much money a particular user had 2 months ago at 1:42 pm, we can just query the needed transactions and sum them up. We know everything with this approach. And this is required to be compliant. As a side note, accounting does the same thing but, of course, they don't call it event sourcing :)&lt;/p&gt;

&lt;p&gt;But event sourcing comes with more advantages, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rebuild state: you can always just discard the app state completely and rebuild it. You have all info you need, all events that ever took place.&lt;/li&gt;
&lt;li&gt;Event replay: if we want to adjust a past event, for example because it was incorrect, we can just do that and rebuild the app state.&lt;/li&gt;
&lt;li&gt;Event replay again: if we have received events in the wrong sequence, which is a common problem with systems that communicate with asynchronous messaging, we can just replay them and get the correct state.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Alternatives to Event Sourcing
&lt;/h4&gt;

&lt;p&gt;Event sourcing definitely solves the auditability/compliance problem. But there are alternatives:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Audit Log Pattern&lt;/strong&gt;: Keep the current state tables but add comprehensive audit logs that track all changes. This is simpler to implement but doesn't provide the same level of detail as event sourcing. You track what changed, but not necessarily the business intent behind the change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Change Data Capture (CDC)&lt;/strong&gt;: Use database-level tools to capture all changes automatically. Tools like Debezium can stream database changes, but this is more technical and less business-focused than event sourcing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Temporal Tables&lt;/strong&gt;: Use database features (like SQL Server's temporal tables) to automatically version data. This provides history but lacks the rich business context that events provide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Transaction Log Mining&lt;/strong&gt;: Extract historical data from database transaction logs. This is complex and database-specific, making it harder to maintain.&lt;/p&gt;

&lt;h3&gt;
  
  
  CQRS (Command Query Responsibility Segregation)
&lt;/h3&gt;

&lt;p&gt;The second major architectural decision was implementing CQRS, though we didn't start with it immediately due to complexity. We kept it in mind during the initial design and tested it later through a proof of concept, then implemented it in production.&lt;/p&gt;

&lt;p&gt;Here's what CQRS means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CQRS = Separate your reads from your writes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is all. Often CQRS is presented as (among other things) having two separate DBs, one for writing and one for reading. But this is not true, you are doing CQRS already when you just separate read and write code, for example by putting them into separate classes.&lt;/p&gt;

&lt;p&gt;However, the benefits we needed do indeed require separate DBs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Traditional Approach:
┌─────────────┐    ┌──────────────┐    ┌──────────────┐
│   Client    │────│   Service    │────│   Database   │
│             │    │              │    │              │
│ Read/Write  │    │ Read/Write   │    │ Read/Write   │
└─────────────┘    └──────────────┘    └──────────────┘

CQRS Approach:
┌─────────────┐    ┌──────────────┐    ┌──────────────┐
│   Client    │────│ Command Side │────│ Write Store  │
│             │    │   (Writes)   │    │ (Event Store)│
│             │    └──────────────┘    └──────────────┘
│             │           │                    │
│             │           │ Events             │ Events
│             │           ▼                    ▼
│             │    ┌──────────────┐    ┌──────────────┐
│             │────│  Query Side  │────│  Read Store  │
│             │    │   (Reads)    │    │ (Projections)│
└─────────────┘    └──────────────┘    └──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More about CQRS &lt;a href="https://martinfowler.com/bliki/CQRS.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The benefits of doing this are the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scale read and write resources differently

&lt;ul&gt;
&lt;li&gt;By having two separate DBs, you can choose different technologies and scale them independently&lt;/li&gt;
&lt;li&gt;If performance is critical in your app, this can definitely help, especially when reads and writes are not of a similar amount&lt;/li&gt;
&lt;li&gt;In our case, we have a read heavy app&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;You can have different models for reading and writing&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;As hinted already, this was crucial for our trading platform because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complex reports and dashboards need denormalized, optimized read models,&lt;/li&gt;
&lt;li&gt;Read and write loads are completely different in trading systems, so we need independent scalability,&lt;/li&gt;
&lt;li&gt;We can use different databases optimized for each purpose.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, CQRS with separate DBs comes at great cost again, for example, you need to deal with eventual consistency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important note&lt;/strong&gt;: We do NOT use CQRS on every service but only where it justifies the complexity.&lt;/p&gt;

&lt;h4&gt;
  
  
  Alternatives to CQRS
&lt;/h4&gt;

&lt;p&gt;You can try to get the benefits of CQRS in other ways, for example by using caching strategies and read replicas. I'll dive into the tradeoffs of these approaches in the detailed discussion section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Microservices
&lt;/h3&gt;

&lt;p&gt;We also decided to break the monolith into microservices. The main reason for this decision was again independent scalability and higher fault tolerance. The existing monolith was often running on very high CPU usage due to report generation and real-time market data processing consuming most resources.&lt;/p&gt;

&lt;p&gt;By separating these concerns into different services, even if our report generation service crashes due to heavy usage, other critical services like transaction processing are not impacted at all. This improves our overall system availability (MTBF - Mean Time Between Failures) and reduces recovery time (MTTR - Mean Time To Recovery).&lt;/p&gt;

&lt;p&gt;An interesting part here was the migration from monolith to microservices using the strangler fig pattern, gradually replacing parts of the monolith.&lt;/p&gt;

&lt;h3&gt;
  
  
  Asynchronous Messaging
&lt;/h3&gt;

&lt;p&gt;Another decision was to use asynchronous messaging for inter-service communication instead of request-response communication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Synchronous (Traditional):
Service A ──HTTP Request──► Service B
          ◄──Response─────
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Asynchronous (Our Approach):
Service A ──Event──► Message Queue ──Event──► Service B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This event-driven approach has many benefits such as high decoupling. However, we were primarily interested in better fault tolerance:&lt;/p&gt;

&lt;p&gt;Suppose Service A informs Service B to save data to its DB. If we use a traditional HTTP request and Service B is down, then the request is lost. Of course there are ways to combat this but if we use asynchronous messaging instead, then Service A just pushed that event to the message queue and if Service B is down, nothing happens. The event just stays on the queue. And as soon as Service B is up again, the event gets processed.&lt;/p&gt;

&lt;p&gt;So using this approach gives us better fault tolerance in the case of network partitions.&lt;/p&gt;

&lt;p&gt;Now asynchronous messaging has clear downsides too, mainly complexity, particularly when it comes to debugging, testing and things of that kind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detailed Discussion: Tradeoffs and Alternatives
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Microservices Deep Dive
&lt;/h3&gt;

&lt;p&gt;We identified services based on business capabilities as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Transaction-Portfolio Service:
├── Owns: Account balances, transaction history, stock holdings
├── Responsibilities: Money transfers, buy/sell orders, balance queries
└── Database: PostgreSQL (ACID compliance critical)

Notification Service:
├── Owns: User preferences, notification history
├── Responsibilities: Email, SMS, push notifications
└── Database: MongoDB (flexible schema for different notification types)
└── Event Sourcing: NOT used (simple CRUD operations)

Social Service:
├── Owns: Posts, likes, comments
├── Responsibilities: Social feed, user interactions
└── Database: MongoDB
└── Event Sourcing: NOT used (not critical for compliance)

Report Service:
├── Owns: Aggregated data, report templates
├── Responsibilities: Generate complex reports
└── Database: ClickHouse (optimized for analytics)
└── CQRS: Read-only projections from other services

User Service:
├── Owns: User profiles, authentication
├── Responsibilities: Registration, login, profile management
└── Database: PostgreSQL
└── Event Sourcing: NOT used (user profiles change infrequently)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Service Boundary Evolution&lt;/strong&gt;: Initially, we considered separating Transaction Service and Portfolio Service. However, we discovered early in the design phase that this would be wrong. Due to very frequent boundary crossings and the need for distributed transactions when a trade affects both account balance and portfolio holdings, we decided to keep these as a single service. This eliminated the complexity of distributed transactions while maintaining other benefits.&lt;/p&gt;

&lt;p&gt;In my opinion, the need of distributed transactions or sagas is always an indicator to check if your service boundaries are the right choice. Maybe you want to merge services instead. To quote Sam Newman in &lt;em&gt;Building Microservices (2nd edition)&lt;/em&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Distributed Transactions: Just Say No. For all the reasons outlined so far, I strongly suggest you avoid the use of distributed transactions like the two-phase commit to coordinate changes in state across your microservices. So what else can you do? Well, the first option could be to just not split the data apart in the first place. If you have pieces of state that you want to manage in a truly atomic and consistent way, and you cannot work out how to sensibly get these characteristics without an ACID-style transaction, then leave that state in a single database, and leave the functionality that manages that state in a single service (or in your monolith). If you're in the process of working out where to split your monolith and what decompositions might be easy (or hard), then you could well decide that splitting apart data that is currently managed in a transaction is just too difficult to handle right now. Work on some other area of the system, and come back to this later. But what happens if you really do need to break this data apart, but you don't want all the pain of managing distributed transactions? In cases like this, you may consider an alternative approach: sagas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So he also recommends to either merge the services or, if really needed, to use sagas. In our case we decided that this service boundary would be wrong since the scalibility needs to the transaction service and the portfolio service are not that different actually.&lt;/p&gt;

&lt;h4&gt;
  
  
  Where We Use Event Sourcing
&lt;/h4&gt;

&lt;p&gt;We used event sourcing only in the Transaction-Portfolio Service due to the strict compliance requirements for financial data. The other services used traditional CRUD patterns since they didn't require the same level of auditability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Event Sourcing Deep Dive
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Benefits of Event Sourcing
&lt;/h4&gt;

&lt;p&gt;Event sourcing has better performance when it comes to writing. Consider this example:&lt;/p&gt;

&lt;p&gt;Traditional Update Pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;current_balance&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Requires&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="n"&gt;locking&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;potential&lt;/span&gt; &lt;span class="n"&gt;contention&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Event Sourcing Pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'deposit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it has even more advantages on the writing part:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write Performance&lt;/strong&gt;: Append-only writes are much faster than updates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Lock Contention&lt;/strong&gt;: Multiple transactions can write simultaneously&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better Concurrency&lt;/strong&gt;: No need to lock rows for balance updates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimized for SSD&lt;/strong&gt;: Sequential writes perform excellently on modern storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although we talked about this already, here is a sample of how you could implement &lt;em&gt;summing an event replay&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Regulatory Question: "What was Account X's balance on Date Y at Time Z?"&lt;/span&gt;
&lt;span class="c1"&gt;-- Event Sourcing Answer:&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="s1"&gt;'Y Z'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Challenges and Solutions
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Performance Issue - Event Replay&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The main challenge we faced was performance degradation when reconstructing current state from thousands of events. For active trading accounts, we had up to 50,000 events per day.&lt;/p&gt;

&lt;p&gt;Our solution was a hybrid approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Event-Based Snapshots&lt;/strong&gt;: Create snapshots after every 1,000 events per account&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delta Replay&lt;/strong&gt;: Only replay events since the last snapshot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach ensures that we never need to replay more than 1,000 events for any account, keeping reconstruction time predictable and fast.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Sample code&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;current_balance&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;account_snapshots&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sequence_number&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_event_sequence&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_event_sequence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_event_sequence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;account_snapshots&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reduced our balance calculation time from 2-5 seconds to 50-200ms for active accounts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storage Growth&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Events accumulate rapidly. We implemented a tiered storage strategy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hot storage&lt;/strong&gt; (Azure Premium SSD): Last 3 months ~ 2TB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Warm storage&lt;/strong&gt; (Azure Standard SSD): 3-12 months ~ 5TB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold storage&lt;/strong&gt; (Azure Archive): 1+ years ~ 50TB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total storage costs: $800/month vs $15,000/month if everything was on premium storage.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tradeoffs
&lt;/h4&gt;

&lt;p&gt;Event sourcing adds significant complexity. A part of the team needed training.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query Complexity:&lt;/strong&gt;&lt;br&gt;
Getting current state requires aggregation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Current Balance Query:&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;current_balance&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;

&lt;span class="c1"&gt;-- vs Traditional:&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Storage Growth:&lt;/strong&gt;&lt;br&gt;
Events accumulate over time and require storage management strategies.&lt;/p&gt;
&lt;h4&gt;
  
  
  Why We Rejected Alternatives
&lt;/h4&gt;

&lt;p&gt;Yes, we have decided to use event sourcing even though it comes with read performance issues - and performance was a main concern of our customer.&lt;/p&gt;

&lt;p&gt;The reason is that event sourcing is simply much superior when it comes to audits. This was much more important to the customer than performance. Plus we managed to solve the performance issue.&lt;/p&gt;
&lt;h3&gt;
  
  
  CQRS Deep Dive
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Very important note&lt;/strong&gt;: CQRS in the sense of having multiple DBs adds complexity and eventual consistency. This is why we decided &lt;strong&gt;against&lt;/strong&gt; using it immediately but just kept it in mind. We later created a proof of concept to compare the performance benefits we would get in the Portfolio Service.&lt;/p&gt;

&lt;p&gt;Our POC results showed for a test user account:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Report generation time&lt;/strong&gt;: 30 seconds → 10 seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dashboard load time&lt;/strong&gt;: 1 second → 400ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex query performance&lt;/strong&gt;: about 2x improvement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result convinced us to implement it. We later added it to the Transaction Service for high-volume trading operations, but &lt;strong&gt;not to all services&lt;/strong&gt;. Adding CQRS to all our services would have little benefits (we don't need the performance benefits or different read/write models at most services) but much complexity.&lt;/p&gt;
&lt;h4&gt;
  
  
  Implementation Details
&lt;/h4&gt;

&lt;p&gt;We implemented CQRS for the Transaction-Portfolio Service as follows. We had a Postgres DB for the write side (command side) and a MongoDB for the read side (query side). We chose a document store because we did not want a fixed schema plus we wanted very high read throughput.&lt;/p&gt;

&lt;p&gt;So the service received a request and decided to write, it wrote to the Postgres DB and also emitted an event to our message broker (Azure Service Bus). This event was then processed by a different instance of the Transaction-Portfolio Service and we write to the MongoDB. Here we don't write the same data but a denormalized form, so that querying the data we need is faster.&lt;/p&gt;

&lt;p&gt;Note we sacrifice ACID by doing this. This gave us eventual consistency between read and write sides, typically within 100-500ms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why This Was Faster&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The performance improvements came from:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Denormalized Read Models&lt;/strong&gt;: Instead of complex JOINs across normalized tables, we had pre-computed aggregations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimized Indexes&lt;/strong&gt;: Each MongoDB collection had indexes tailored for specific query patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate Scaling&lt;/strong&gt;: We could scale read replicas independently of the write database&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Consider this example of generating a user's portfolio performance report:&lt;/p&gt;

&lt;p&gt;Traditional approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Complex query with multiple JOINs and aggregations&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;total_shares&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;avg_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="c1"&gt;-- ... more complex calculations&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;portfolios&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;portfolio_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;
&lt;span class="c1"&gt;-- This took up to 30 seconds for active users&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CQRS approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simple document lookup from pre-computed projection&lt;/span&gt;
&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;portfolio_summaries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// fast&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Major Challenges and How We Solved Them
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Debugging Distributed Systems&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was our biggest pain point initially. When a transaction failed, tracing the issue across multiple services and async message queues was a nightmare.&lt;/p&gt;

&lt;p&gt;We solved this by implementing distributed tracing with correlation IDs that flow through every service call and message. Every log entry includes the correlation ID, making it possible to reconstruct the entire flow. We used Jaeger for distributed tracing and structured logging with consistent fields across all services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Testing Complexity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Testing event sourcing and CQRS systems is fundamentally different. You can't just mock database calls - you need to verify that events are produced correctly and that projections are updated properly.&lt;/p&gt;

&lt;p&gt;We created integration test environments that could replay production events against test instances. This allowed us to validate that code changes wouldn't break existing event processing. We also invested heavily in property-based testing to verify that event sequences always produce valid states.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Would I Change?
&lt;/h3&gt;

&lt;p&gt;I'm convinced this was the right architecture for our specific requirements. However, there are definitely things I would approach differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We didn't have a clear strategy for evolving event schemas initially. When we needed to add fields to events or change event structure, it created compatibility issues with existing events.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Also our monitoring and logging was weak in the beginning and made everything even more complex to start.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I would consider using EventStore instead of Postgres for the Transaction-Portfolio Service. EventStore is purpose-built for event sourcing and provides features like built-in projections, event versioning, and optimized append-only storage. This would eliminate much of the custom event sourcing infrastructure we had to build on top of Postgres.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cqrs</category>
      <category>eventdriven</category>
      <category>microservices</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Consistent Hashing Explained</title>
      <dc:creator>Lukas Niessen</dc:creator>
      <pubDate>Sun, 25 May 2025 10:26:24 +0000</pubDate>
      <link>https://dev.to/lukasniessen/consistent-hashing-explained-2g0f</link>
      <guid>https://dev.to/lukasniessen/consistent-hashing-explained-2g0f</guid>
      <description>&lt;p&gt;This contains an ELI5 and a deeper explanation of consistent hashing. I have added much ASCII art, hehe :) At the end, I even added a simplified example code of how you could implement consistent hashing.&lt;/p&gt;

&lt;h1&gt;
  
  
  ELI5: Consistent Pizza Hashing 🍕
&lt;/h1&gt;

&lt;p&gt;Suppose you're at a pizza party with friends. Now you need to decide who gets which pizza slices.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Bad Way (Simple Hash)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You have 3 friends: Alice, Bob, and Charlie&lt;/li&gt;
&lt;li&gt;For each pizza slice, you count: "1-Alice, 2-Bob, 3-Charlie, 1-Alice, 2-Bob..."&lt;/li&gt;
&lt;li&gt;Slice #7 → 7 ÷ 3 = remainder 1 → Alice gets it&lt;/li&gt;
&lt;li&gt;Slice #8 → 8 ÷ 3 = remainder 2 → Bob gets it
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;With 3 friends:
Slice 7 → Alice
Slice 8 → Bob
Slice 9 → Charlie
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; Your friend Dave shows up. Now you have 4 friends. So we need to do the distribution again.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slice #7 → 7 ÷ 4 = remainder 3 → Dave gets it (was Alice's!)&lt;/li&gt;
&lt;li&gt;Slice #8 → 8 ÷ 4 = remainder 0 → Alice gets it (was Bob's!)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;With 4 friends:
Slice 7 → Dave (moved from Alice!)
Slice 8 → Alice (moved from Bob!)
Slice 9 → Bob (moved from Charlie!)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Almost EVERYONE'S pizza has moved around...! 😫&lt;/p&gt;

&lt;h3&gt;
  
  
  The Good Way (Consistent Hashing)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Draw a big circle and put your friends around it&lt;/li&gt;
&lt;li&gt;Each pizza slice gets a number that points to a spot on the circle&lt;/li&gt;
&lt;li&gt;Walk clockwise from that spot until you find a friend - he gets the slice.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;           Alice
      🍕7       .
      .            .
     .               .
   Dave      ○       Bob
     .              🍕8
      .             .
       .           .
          Charlie

🍕7 walks clockwise and hits Alice
🍕8 walks clockwise and hits Charlie
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When Dave joins:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dave sits between Bob and Charlie&lt;/li&gt;
&lt;li&gt;Only slices that were "between Bob and Dave" move from Charlie to Dave&lt;/li&gt;
&lt;li&gt;Everyone else keeps their pizza! 🎉
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;           Alice
      🍕7       .
      .            .
     .               .
   Dave      ○       Bob
     .              🍕8
      .             .
       .          Dave
          Charlie

🍕7 walks clockwise and hits Alice (nothing changed)
🍕8 walks clockwise and hits Dave (change)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Back to the real world
&lt;/h2&gt;

&lt;p&gt;This was an ELI5 but the reality is not much harder.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instead of pizza slices, we have &lt;strong&gt;data&lt;/strong&gt; (like user photos, messages, etc)&lt;/li&gt;
&lt;li&gt;Instead of friends, we have &lt;strong&gt;servers&lt;/strong&gt; (computers that store data)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the &lt;em&gt;"circle strategy"&lt;/em&gt; from above we distribute the data evenly across our servers and when we add new servers, not much of the data needs to relocate. This is exactly the goal of consistent hashing.&lt;/p&gt;

&lt;h2&gt;
  
  
  In a "Simplified Nutshell"
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Make a circle&lt;/strong&gt; (hash ring)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put servers around the circle&lt;/strong&gt; (like friends around pizza)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put data around the circle&lt;/strong&gt; (like pizza slices)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Walk clockwise&lt;/strong&gt; to find which server stores each piece of data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When servers join/leave&lt;/strong&gt; → only nearby data moves&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it! Consistent hashing keeps your data organized, also when your system grows or shrinks.&lt;/p&gt;

&lt;p&gt;So as we saw, consistent hashing solves problems of database partitioning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Distribute equally across nodes,&lt;/li&gt;
&lt;li&gt;When adding or removing servers, keep the "relocating-efforts" low.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why It's Called Consistent?
&lt;/h3&gt;

&lt;p&gt;Because it's consistent in the sense of adding or removing one server doesn't mess up where everything else is stored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Non-ELI5 Explanatiom
&lt;/h2&gt;

&lt;p&gt;Here the explanation again, briefly, but non-ELI5 and with some more details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create the Hash Ring
&lt;/h3&gt;

&lt;p&gt;Think of a circle with points from 0 to some large number. For simplicity, let's use 0 to 100 - in reality it's rather 0 to 2^32!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    0/100
                      │
               95 ────┼──── 5
                     ╱│╲
                90 ╱  │  ╲ 10
                  ╱   │   ╲
              85 ╱    │    ╲ 15
                ╱     │     ╲
           80 ─┤      │      ├─ 20
              ╱       │       ╲
          75 ╱        │        ╲ 25
            ╱         │         ╲
       70 ─┤          │          ├─ 30
          ╱           │           ╲
      65 ╱            │            ╲ 35
        ╱             │             ╲
   60 ─┤              │              ├─ 40
      ╱               │               ╲
  55 ╱                │                ╲ 45
    ╱                 │                 ╲
50 ─┤                 │                 ├─ 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Place Databases on the Ring
&lt;/h3&gt;

&lt;p&gt;We distribute our databases evenly around the ring. With 4 databases, we might place them at positions 0, 25, 50, and 75:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    0/100
                   [DB1]
               95 ────┼──── 5
                     ╱│╲
                90 ╱  │  ╲ 10
                  ╱   │   ╲
              85 ╱    │    ╲ 15
                ╱     │     ╲
           80 ─┤      │      ├─ 20
              ╱       │       ╲
    [DB4] 75 ╱        │        ╲ 25 [DB2]
            ╱         │         ╲
       70 ─┤          │          ├─ 30
          ╱           │           ╲
      65 ╱            │            ╲ 35
        ╱             │             ╲
   60 ─┤              │              ├─ 40
      ╱               │               ╲
  55 ╱                │                ╲ 45
    ╱                 │                 ╲
50 ─┤               [DB3]               ├─ 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Find Events on the Ring
&lt;/h3&gt;

&lt;p&gt;To determine which database stores an event:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hash the event ID to get a position on the ring&lt;/li&gt;
&lt;li&gt;Walk clockwise from that position until you hit a database&lt;/li&gt;
&lt;li&gt;That's your database
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Example Event Placements:

Event 1001: hash(1001) % 100 = 8
8 → walk clockwise → hits DB2 at position 25

Event 2002: hash(2002) % 100 = 33
33 → walk clockwise → hits DB3 at position 50

Event 3003: hash(3003) % 100 = 67
67 → walk clockwise → hits DB4 at position 75

Event 4004: hash(4004) % 100 = 88
88 → walk clockwise → hits DB1 at position 0/100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Minimal Redistribution
&lt;/h2&gt;

&lt;p&gt;Now here's where consistent hashing shines. When you add a fifth database at position 90:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before Adding DB5:
Range 75-100: All events go to DB1

After Adding DB5 at position 90:
Range 75-90:  Events now go to DB5 ← Only these move!
Range 90-100: Events still go to DB1

Events affected: Only those with hash values 75-90
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only events that hash to the range between 75 and 90 need to move. Everything else stays exactly where it was. No mass redistribution.&lt;/p&gt;

&lt;p&gt;The same principle applies when removing databases. Remove DB2 at position 25, and only events in the range 0-25 need to move to the next database clockwise (DB3).&lt;/p&gt;

&lt;h2&gt;
  
  
  Virtual Nodes: Better Load Distribution
&lt;/h2&gt;

&lt;p&gt;There's still one problem with this basic approach. When we remove a database, all its data goes to the next database clockwise. This creates uneven load distribution.&lt;/p&gt;

&lt;p&gt;The solution is &lt;em&gt;virtual nodes&lt;/em&gt;. Instead of placing each database at one position, we place it at multiple positions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Each database gets 5 virtual nodes (positions):

DB1: positions 0, 20, 40, 60, 80
DB2: positions 5, 25, 45, 65, 85
DB3: positions 10, 30, 50, 70, 90
DB4: positions 15, 35, 55, 75, 95
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when DB2 is removed, its load gets distributed across multiple databases instead of dumping everything on one database.&lt;/p&gt;

&lt;h2&gt;
  
  
  When You'll Need This?
&lt;/h2&gt;

&lt;p&gt;Usually, you will not want to actually implement this yourself unless you're designing a single scaled custom backend component, something like designing a custom distributed cache, design a distributed database or design a distributed message queue.&lt;/p&gt;

&lt;p&gt;Popular systems do use consistent hashing under the hood for you already - for example Redis, Cassandra, DynamoDB, and most CDN networks do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation in JavaScript
&lt;/h2&gt;

&lt;p&gt;Here's a complete implementation of consistent hashing. Please note that this is of course simplified.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConsistentHash&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;virtualNodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;virtualNodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;virtualNodes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ring&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// position -&amp;gt; server&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;servers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sortedPositions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="c1"&gt;// sorted array of positions for binary search&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Hash function using MD5&lt;/span&gt;
  &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;md5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="mi"&gt;16&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Add a server to the ring&lt;/span&gt;
  &lt;span class="nf"&gt;addServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; already exists`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Add virtual nodes for this server&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;virtualNodes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;virtualKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;virtualKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateSortedPositions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`Added server &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;virtualNodes&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; virtual nodes`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Remove a server from the ring&lt;/span&gt;
  &lt;span class="nf"&gt;removeServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; doesn't exist`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Remove all virtual nodes for this server&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;virtualNodes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;virtualKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;virtualKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateSortedPositions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Removed server &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Update sorted positions array for efficient lookups&lt;/span&gt;
  &lt;span class="nf"&gt;updateSortedPositions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sortedPositions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Find which server should handle this key&lt;/span&gt;
  &lt;span class="nf"&gt;getServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sortedPositions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No servers available&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Binary search for the first position &amp;gt;= our hash&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sortedPositions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sortedPositions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// If we're past the last position, wrap around to the first&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverPosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sortedPositions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sortedPositions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sortedPositions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serverPosition&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Get distribution statistics&lt;/span&gt;
  &lt;span class="nf"&gt;getDistribution&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Test with 10000 sample keys&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`key_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Show ring state (useful for debugging)&lt;/span&gt;
  &lt;span class="nf"&gt;showRing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Ring state:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sortedPositions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Position &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example usage and testing&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;demonstrateConsistentHashing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;=== Consistent Hashing Demo ===&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hashRing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ConsistentHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 3 virtual nodes per server for clearer demo&lt;/span&gt;

  &lt;span class="c1"&gt;// Add initial servers&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1. Adding initial servers...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;hashRing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;server1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;hashRing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;server2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;hashRing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;server3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Test key distribution&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;2. Testing key distribution with 3 servers:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event_1234&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event_5678&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event_9999&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event_4567&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event_8888&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hashRing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hashRing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (hash: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) -&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Show distribution statistics&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;3. Distribution across 10,000 keys:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hashRing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDistribution&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;percentage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; keys (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Add a new server and see minimal redistribution&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;4. Adding server4...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;hashRing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;server4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;5. Same events after adding server4:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;moved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stayed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hashRing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hashRing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (hash: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) -&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;newServer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Note: In a real implementation, you'd track the old assignments&lt;/span&gt;
    &lt;span class="c1"&gt;// This is just for demonstration&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;6. New distribution with 4 servers:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;distribution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hashRing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDistribution&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;percentage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; keys (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Remove a server&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;7. Removing server2...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;hashRing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;server2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;8. Distribution after removing server2:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;distribution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hashRing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDistribution&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;percentage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; keys (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Demonstrate the redistribution problem with simple modulo&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;demonstrateSimpleHashing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;=== Simple Hash + Modulo (for comparison) ===&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;simpleHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;md5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="mi"&gt;16&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getServerSimple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;numServers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`server&lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nf"&gt;simpleHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;numServers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event_1234&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event_5678&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event_9999&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event_4567&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event_8888&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;With 3 servers:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assignments3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getServerSimple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;assignments3&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;With 4 servers:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;moved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getServerSimple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assignments3&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (MOVED from &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;assignments3&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;moved&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (stayed)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`\nResult: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;moved&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; events moved (&lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moved&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
      &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;%)`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Run the demonstrations&lt;/span&gt;
&lt;span class="nf"&gt;demonstrateConsistentHashing&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;demonstrateSimpleHashing&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Code Notes
&lt;/h2&gt;

&lt;p&gt;The implementation has several key components:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hash Function&lt;/strong&gt;: Uses MD5 to convert keys into positions on the ring. In production, you might use faster hashes like Murmur3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Virtual Nodes&lt;/strong&gt;: Each server gets multiple positions on the ring (150 by default) to ensure better load distribution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Binary Search&lt;/strong&gt;: Finding the right server uses binary search on sorted positions for O(log n) lookup time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ring Management&lt;/strong&gt;: Adding/removing servers updates the ring and maintains the sorted position array.&lt;/p&gt;

&lt;p&gt;Do not use this code for real-world usage, it's just sample code. A few things that you should do different in real examples for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hash Function&lt;/strong&gt;: Use faster hashes like Murmur3 or xxHash instead of MD5&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Virtual Nodes&lt;/strong&gt;: More virtual nodes (100-200) provide better distribution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistence&lt;/strong&gt;: Store ring state in a distributed configuration system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replication&lt;/strong&gt;: Combine with replication strategies for fault tolerance&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>database</category>
      <category>programming</category>
      <category>data</category>
    </item>
    <item>
      <title>Single Computers vs Distributed Systems: Why Everything Gets Complicated</title>
      <dc:creator>Lukas Niessen</dc:creator>
      <pubDate>Sun, 25 May 2025 06:54:57 +0000</pubDate>
      <link>https://dev.to/lukasniessen/single-computers-vs-distributed-systems-why-everything-gets-complicated-1j4k</link>
      <guid>https://dev.to/lukasniessen/single-computers-vs-distributed-systems-why-everything-gets-complicated-1j4k</guid>
      <description>&lt;h2&gt;
  
  
  The Predictable World of Single Computers
&lt;/h2&gt;

&lt;p&gt;One machine is pretty straightforward. For example your laptop. When you run a program, it either works or it doesn't. In the sense if that if something goes wrong, the whole system usually crashes rather than giving you wrong answers. And by &lt;em&gt;going wrong&lt;/em&gt; I don't mean software bugs because the OS and the hardware just think this way intentional. I mean 'bugs' at hardware level.&lt;/p&gt;

&lt;p&gt;And this is actually by design. Computers are built to fail rather than produce false results.&lt;/p&gt;

&lt;p&gt;This deterministic behavior hides all the messy details of physical hardware. Your CPU might have tiny manufacturing defects, your RAM might have occasional bit flips, but the system handles these gracefully by either working correctly or failing completely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distributed Systems
&lt;/h2&gt;

&lt;p&gt;The moment you connect multiple computers over a network, everything changes. Now you're dealing with the messy reality of the physical world, and things get unpredictable fast.&lt;/p&gt;

&lt;p&gt;Of course every node itself is still deterministic but there are many other issues. Such as that network cables fail, power goes out in one data center but not another, and that innocent-looking Ethernet switch might just decide to drop packets for no good reason. Suddenly you have &lt;strong&gt;partial failures&lt;/strong&gt; - some parts of your system work while others don't, and you might not even know which is which.&lt;/p&gt;

&lt;p&gt;This creates nondeterministic behavior. Did your database write succeed? Maybe. Is the user's payment processed? Who knows - the network timeout doesn't tell you if the transaction went through or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Different Approaches to Handling This Mess
&lt;/h2&gt;

&lt;p&gt;When you're building large-scale systems, you basically have two philosophical approaches to dealing with these inevitable failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  The HPC Approach: If Anything Breaks, Start Over
&lt;/h3&gt;

&lt;p&gt;High-performance computing systems like supercomputers take the single-computer approach and scale it up. They use specialized, expensive, reliable hardware with fancy interconnects like shared memory and RDMA for lightning-fast communication.&lt;/p&gt;

&lt;p&gt;When something goes wrong, they don't mess around with partial failures. Instead, they:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stop everything&lt;/li&gt;
&lt;li&gt;Roll back to the last checkpoint&lt;/li&gt;
&lt;li&gt;Restart the entire computation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's like treating a 10,000-node supercomputer as one giant computer that either works or doesn't. This makes sense when you're running a weather simulation that can afford to restart, but it doesn't work so well for systems that need to stay online.&lt;/p&gt;

&lt;p&gt;This approach is known as &lt;em&gt;vertical scaling&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Cloud Approach: Keep Going No Matter What
&lt;/h3&gt;

&lt;p&gt;Cloud computing takes the opposite approach. Instead of expensive, reliable hardware, cloud systems use cheap commodity machines that fail all the time. The assumption is that something is always breaking somewhere.&lt;/p&gt;

&lt;p&gt;Rather than stopping everything when a node fails, cloud systems are designed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep running even when parts fail&lt;/li&gt;
&lt;li&gt;Route around problems automatically&lt;/li&gt;
&lt;li&gt;Replace broken components without downtime&lt;/li&gt;
&lt;li&gt;Handle rolling updates and gradual replacements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach powers every internet service you use. When you're browsing Netflix, some server somewhere is probably crashing, but you never notice because the system just routes around the problem.&lt;/p&gt;

&lt;p&gt;This approach is known as &lt;em&gt;horizontal scaling&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traditional Enterprise: The Middle Ground
&lt;/h3&gt;

&lt;p&gt;Most corporate data centers fall somewhere between these approaches, trying to balance reliability with cost. They're more reliable than cloud commodity hardware but less specialized than supercomputers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Cloud Systems Are So Much Harder to Build
&lt;/h2&gt;

&lt;p&gt;Here's the thing about building fault-tolerant distributed systems: they're ridiculously complex.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scale makes everything worse.&lt;/strong&gt; A system with 1,000 nodes has way more failure modes than a system with 10 nodes. Components fail more often, network partitions happen regularly, and debugging becomes a nightmare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cheap hardware fails constantly.&lt;/strong&gt; Cloud providers use commodity hardware because it's cost-effective, but that means higher failure rates. The system has to be smart enough to work around constant small failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network complexity explodes.&lt;/strong&gt; Instead of the specialized network topologies supercomputers use (like fancy meshes and toruses), cloud systems rely on IP/Ethernet with Clos network topologies to handle massive bandwidth needs.&lt;/p&gt;

&lt;p&gt;But here's the counterintuitive part: you can actually build incredibly reliable systems from unreliable components. Think about it - TCP gives you reliable data transmission over the unreliable internet, and error-correcting codes let you store data reliably on imperfect storage devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line for System Design
&lt;/h2&gt;

&lt;p&gt;If you're building any kind of distributed system, you need to accept that partial failures will happen. There's no avoiding it.&lt;/p&gt;

&lt;p&gt;Even small systems need to plan for faults because they will eventually occur. You can't just hope your two-server setup will never have problems - it will, and probably sooner than you think.&lt;/p&gt;

&lt;p&gt;The key is building fault tolerance into your software design from the beginning. Test for weird failure scenarios, not just the obvious ones. Plan for network splits, slow nodes, and all the other fun surprises distributed systems throw at you.&lt;/p&gt;

&lt;p&gt;Certainly an interesting read here is the Chaos Monkey of Netflix.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>systemdesign</category>
      <category>architecture</category>
    </item>
    <item>
      <title>ELI5: CAP Theorem in System Design</title>
      <dc:creator>Lukas Niessen</dc:creator>
      <pubDate>Sat, 24 May 2025 16:31:46 +0000</pubDate>
      <link>https://dev.to/lukasniessen/eli5-cap-theorem-in-system-design-2hh4</link>
      <guid>https://dev.to/lukasniessen/eli5-cap-theorem-in-system-design-2hh4</guid>
      <description>&lt;h1&gt;
  
  
  ELI5: CAP Theorem in System Design
&lt;/h1&gt;

&lt;p&gt;This is a super simple ELI5 explanation of the CAP Theorem. After that, I explain a common misunderstanding that you should be careful of, and then lastly, I will give two system design examples where CAP Theorem is used to make design decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Super simple explanation
&lt;/h2&gt;

&lt;p&gt;C = Consistency = Every user gets the same data&lt;br&gt;
A = Availability = Users can retrieve the data always&lt;br&gt;
P = Partition tolerance = Even if there are network issues, everything works fine still&lt;/p&gt;

&lt;p&gt;Now the CAP Theorem states that in a distributed system, you need to decide whether you want consistency or availability. You cannot have both.&lt;/p&gt;
&lt;h3&gt;
  
  
  Questions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;And in non-distributed systems?&lt;/strong&gt; CAP Theorem only applies to distributed systems. If you only have one database, you can totally have both. (Unless that DB server if down obviously, then you have neither.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this always the case?&lt;/strong&gt; No, if everything is &lt;em&gt;green&lt;/em&gt;, we have both, consistency and availability. However, if a server looses internet access for example, or there is any other fault that occurs, THEN we have only one of the two, that is either have consistency or availability.&lt;/p&gt;
&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;As I said already, the problems only arises, when we have some sort of fault. Let's look at this example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    US (Master)                    Europe (Replica)
   ┌─────────────┐                ┌─────────────┐
   │             │                │             │
   │  Database   │◄──────────────►│  Database   │
   │   Master    │    Network     │   Replica   │
   │             │  Replication   │             │
   └─────────────┘                └─────────────┘
        │                              │
        │                              │
        ▼                              ▼
   [US Users]                     [EU Users]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Normal operation:&lt;/strong&gt; Everything works fine. US users write to master, changes replicate to Europe, EU users read consistent data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network partition happens:&lt;/strong&gt; The connection between US and Europe breaks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    US (Master)                    Europe (Replica)
   ┌─────────────┐                ┌─────────────┐
   │             │    ╳╳╳╳╳╳╳     │             │
   │  Database   │◄────╳╳╳╳╳─────►│  Database   │
   │   Master    │    ╳╳╳╳╳╳╳     │   Replica   │
   │             │    Network     │             │
   └─────────────┘     Fault      └─────────────┘
        │                              │
        │                              │
        ▼                              ▼
   [US Users]                     [EU Users]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have two choices:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choice 1: Prioritize Consistency (CP)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EU users get error messages: "Database unavailable"&lt;/li&gt;
&lt;li&gt;Only US users can access the system&lt;/li&gt;
&lt;li&gt;Data stays consistent but availability is lost for EU users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Choice 2: Prioritize Availability (AP)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EU users can still read/write to the EU replica&lt;/li&gt;
&lt;li&gt;US users continue using the US master&lt;/li&gt;
&lt;li&gt;Both regions work, but data becomes inconsistent (EU might have old data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What are Network Partitions?
&lt;/h2&gt;

&lt;p&gt;Network partitions are when parts of your distributed system can't talk to each other. Think of it like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your servers are like people in different rooms&lt;/li&gt;
&lt;li&gt;Network partitions are like the doors between rooms getting stuck&lt;/li&gt;
&lt;li&gt;People in each room can still talk to each other, but can't communicate with other rooms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Common causes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internet connection failures&lt;/li&gt;
&lt;li&gt;Router crashes&lt;/li&gt;
&lt;li&gt;Cable cuts&lt;/li&gt;
&lt;li&gt;Data center outages&lt;/li&gt;
&lt;li&gt;Firewall issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key thing is: &lt;strong&gt;partitions WILL happen&lt;/strong&gt;. It's not a matter of if, but when.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "2 out of 3" Misunderstanding
&lt;/h2&gt;

&lt;p&gt;CAP Theorem is often presented as "pick 2 out of 3." This is wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Partition tolerance is not optional.&lt;/strong&gt; In distributed systems, network partitions will happen. You can't choose to "not have" partitions - they're a fact of life, like rain or traffic jams... :-)&lt;/p&gt;

&lt;p&gt;So our choice is: &lt;strong&gt;When a partition happens, do you want Consistency OR Availability?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CP Systems:&lt;/strong&gt; When a partition occurs → node stops responding to maintain consistency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AP Systems:&lt;/strong&gt; When a partition occurs → node keeps responding but users may get inconsistent data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, it's not "pick 2 out of 3," it's "partitions will happen, so pick C or A."&lt;/p&gt;

&lt;h2&gt;
  
  
  System Design Example 1: Social Media Feed
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Building Netflix&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision:&lt;/strong&gt; Prioritize Availability (AP)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt; If some users see slightly outdated movie names for a few seconds, it's not a big deal. But if the users cannot watch movies at all, they will be very unhappy.&lt;/p&gt;

&lt;h2&gt;
  
  
  System Design Example 2: Flight Booking System
&lt;/h2&gt;

&lt;p&gt;In here, we will not apply CAP Theorem to the entire system but to parts of the system. So we have two different parts with different priorities:&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 1: Flight Search
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Users browsing and searching for flights&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision:&lt;/strong&gt; Prioritize Availability&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt; Users want to browse flights even if prices/availability might be slightly outdated. Better to show approximate results than no results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 2: Flight Booking
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; User actually purchasing a ticket&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision:&lt;/strong&gt; Prioritize Consistency&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt; If we would prioritize availibility here, we might sell the same seat to two different users. Very bad. We need strong consistency here.&lt;/p&gt;

&lt;h3&gt;
  
  
  PS: Architectural Quantum
&lt;/h3&gt;

&lt;p&gt;What I just described, having two different &lt;em&gt;scopes&lt;/em&gt;, is the concept of having more than one &lt;em&gt;architecture quantum&lt;/em&gt;. There is a lot of interesting stuff online to read about the concept of architecture quanta :-)&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>System Design: Choosing the Right Dataflow</title>
      <dc:creator>Lukas Niessen</dc:creator>
      <pubDate>Mon, 19 May 2025 19:17:35 +0000</pubDate>
      <link>https://dev.to/lukasniessen/system-design-choosing-the-right-dataflow-2e35</link>
      <guid>https://dev.to/lukasniessen/system-design-choosing-the-right-dataflow-2e35</guid>
      <description>&lt;p&gt;When building a system, a very important decision is how data moves between components. I will walk through&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database dataflow,&lt;/li&gt;
&lt;li&gt;Service calls (REST, SOAP, GraphQL, RPC),&lt;/li&gt;
&lt;li&gt;Messaging (message queues).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dataflow Through Databases
&lt;/h2&gt;

&lt;p&gt;First off: The DB is almost always deployed separately, that is, a different machine or container. This has many reasons, including the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: You can scale your app and database independently. For example, spin up more app servers without touching the DB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fault Tolerance&lt;/strong&gt;: You can restart your app without risking the DB, or upgrade the DB without downtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: You can isolate the DB behind a firewall or private subnet for tighter access control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(An exception to this is obviously SQLite or other embedded DBs.)&lt;/p&gt;

&lt;p&gt;So we communicate with our DB &lt;strong&gt;over the network&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Protocols
&lt;/h3&gt;

&lt;p&gt;Which protocols are used? Databases don't use HTTP. They rely on custom binary protocols for performance. In the transport layer we use TCP due to its reliability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Considerations
&lt;/h3&gt;

&lt;p&gt;When storing data in a database, it is encoded into a format suitable for the database (eg. UTF-8 for text in PostgreSQL, BSON for documents in MongoDB). When retrieved, the data is decoded for use by the application.&lt;/p&gt;

&lt;p&gt;Something that makes databases unique is that they often store data for decades, such as user profiles or posts from 10 years ago. And they store it in the original encoding or schema. This means the data must remain accessible even as applications evolve and is often summarized as &lt;em&gt;data outlives code&lt;/em&gt;. So we need to ensure backward compatibility (new code can read old data) and forward compatibility (old code can work with new data formats or schemas). This is achieved through strategies like:&lt;/p&gt;

&lt;h2&gt;
  
  
  Dataflow Through Services
&lt;/h2&gt;

&lt;p&gt;While there are many more, I will cover mainly REST vs SOAP vs RPC and I will mention GraphQL without going deep there. Let's first get the terms straight.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Service = An API exposed by a server&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The most common type of communication is: clients and servers. The server exposes an API and the clients can use that API to make requests to server over the network.&lt;/p&gt;

&lt;h3&gt;
  
  
  Web Service
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Web Service = Service that uses HTTP&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Although web services are not exlusively in &lt;em&gt;the web&lt;/em&gt; this is still the name they received. Reminder of how the web works: clients (web browsers) send requests to web servers. For example HTTP GET requests to download HTML, CSS, JavaScript, images or scripts, or HTTP POST requests to submit data, such as a registering form.&lt;/p&gt;

&lt;h3&gt;
  
  
  REST (Representational State Transfer)
&lt;/h3&gt;

&lt;p&gt;REST is &lt;strong&gt;not&lt;/strong&gt; a protocol. It's merely a way to design your service-client or service-service communication. The core ideas are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use simple data formats&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use URLs to identify &lt;strong&gt;resources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;So strictly speaking &lt;code&gt;/profiles/lukas&lt;/code&gt; is cool, but &lt;code&gt;profiles/get-profile&lt;/code&gt; is not that much. The latter is an action, not a resource.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Use HTTP features, for example for cache control, authentication, or content type negotiation.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;REST responses are typically in JSON. There is much more to REST and interpretations also moved pretty far from what it actually was meant to be, but that's all topic for a separate article.&lt;/p&gt;

&lt;p&gt;REST has been gaining popularity compared to SOAP, especially for cross-organizational service integration. The main reason is its simplicity, support and interoperability.&lt;/p&gt;

&lt;h3&gt;
  
  
  SOAP (Simple Object Access Protocol)
&lt;/h3&gt;

&lt;p&gt;SOAP is an XML-based protocol for making network API requests. Although most commonly used over HTTP, it aims to be independent from HTTP and avoids using most HTTP features. Instead, it comes with a complex set of related standards (the WS-* framework) that add various features.&lt;/p&gt;

&lt;p&gt;The API of a SOAP web service is described using an XML-based language called the Web Services Description Language (WSDL). WSDL is not designed to be human-readable. Also SOAP messages are often too complex to construct manually. So develops rely heavily on tool support, code generation, and IDEs. And that's the biggest problem. For programming languages not supported by SOAP vendors, integration is difficult. Although SOAP is standardized, interoperability between different vendors' implementations often causes problems.&lt;/p&gt;

&lt;p&gt;So in a nutshell, SOAP is much more complex and requires tools and the like. This often causes problems and is the main reason SOAP lost popularity (however, it's still used at many places).&lt;/p&gt;

&lt;h3&gt;
  
  
  RPC (Remote Procedure Call)
&lt;/h3&gt;

&lt;p&gt;Now some clients are not a browser or native app or the like. In fact, applications or services can be clients as well. For example, when your service makes a call to another service, your service is a client in the context of this call. So how do we approach such communication?&lt;/p&gt;

&lt;p&gt;RPC is intended for such remote calls and the idea is to make them look like local function calls. However, it's important to clarify that this will never &lt;em&gt;really&lt;/em&gt; work for a multitude of reasons, here are some.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Predictability&lt;/strong&gt;: Local function calls are predictable, succeeding or failing based on controlled parameters. Network requests are unpredictable due to potential network issues, requiring retries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Outcomes&lt;/strong&gt;: Local calls return a result or throw an exception. Network requests may timeout without a result, leaving uncertainty about request success.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Retries&lt;/strong&gt;: Retrying network requests risks multiple executions if responses are lost (there are workarounds).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Response times&lt;/strong&gt;: Local calls have consistent execution times. Network requests are slower with variable latency, ranging from milliseconds to seconds based on network conditions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Parameter Passing&lt;/strong&gt;: Local calls efficiently pass object references. Network requests require encoding parameters into bytes, which is problematic for large objects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Language differences&lt;/strong&gt;: RPC frameworks must translate datatypes across languages (eg. from Java to TypeScript), which can be complex.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most common RPC framework is gRPC (by Google). It uses &lt;em&gt;Protocol Buffers&lt;/em&gt; for encoding the transmitted data.&lt;/p&gt;

&lt;p&gt;The main use of RPC is for requests between services owned by the same organization, usually within the same datacenter. For example between micro services. When done right, eg. by using gRPC in the right way, the requests are very fast (way faster than using a RESTful API). More to this later.&lt;/p&gt;

&lt;h3&gt;
  
  
  GraphQL
&lt;/h3&gt;

&lt;p&gt;GraphQL is a query language for APIs and a runtime for executing those queries. Unlike REST, where each endpoint returns a fixed data structure, GraphQL lets clients specify exactly what data they need.&lt;/p&gt;

&lt;p&gt;In simple terms, GraphQL works like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The client sends a single query describing the data it needs&lt;/li&gt;
&lt;li&gt;The server returns exactly that data, nothing more, nothing less&lt;/li&gt;
&lt;li&gt;Everything happens over a single endpoint (typically &lt;code&gt;/graphql&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GraphQL was developed by Facebook in 2012 and released as open source in 2015. It arose from the need to efficiently fetch data for mobile applications with varying requirements and limited bandwidth.&lt;/p&gt;

&lt;p&gt;Key advantages of GraphQL include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Precise data fetching&lt;/strong&gt;: Clients get exactly what they ask for, reducing over-fetching or under-fetching of data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single request&lt;/strong&gt;: Clients can retrieve multiple resources in a single request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strong typing&lt;/strong&gt;: The schema defines what queries are possible, enabling better tooling and validation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioning&lt;/strong&gt;: Fields can be deprecated without breaking existing queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main trade-offs include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More complex server implementation than simple REST&lt;/li&gt;
&lt;li&gt;Potential performance issues with deeply nested queries&lt;/li&gt;
&lt;li&gt;Caching is more challenging than with REST&lt;/li&gt;
&lt;li&gt;Learning curve for teams new to the technology&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GraphQL is particularly well-suited for complex UIs with changing data requirements and diverse clients with different needs - like Facebook or Instagram for example&lt;/p&gt;

&lt;h2&gt;
  
  
  Serialization and De-serialization
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Serialization = converting data structures into a transmittable format`&lt;/p&gt;

&lt;p&gt;De-serialization = reconstructing data structures from the received format`&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We clearly need serialization/de-serialization when communicating over the network. So it's important to get it right. There are studies, for example &lt;a href="https://arxiv.org/abs/2204.03032" rel="noopener noreferrer"&gt;this one&lt;/a&gt;, which show that often serialization/de-serialization accounts for 80% or more of the communication time in microservice architectures.&lt;/p&gt;

&lt;p&gt;This shows the importance of using gRPC (or other frameworks). There we still have serialization/de-serialization but it's very efficient - much faster than typical JSON serialization/de-serialization with RESTful APIs for example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dataflow Through Messaging
&lt;/h2&gt;

&lt;p&gt;At its simplest, messaging is like leaving a note for someone when they're not immediately available. Rather than connecting directly, you drop a message in a queue and trust it will be delivered.&lt;/p&gt;

&lt;h3&gt;
  
  
  Super Brief Explanation
&lt;/h3&gt;

&lt;p&gt;Messaging systems operate with a few key components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Producers&lt;/strong&gt;: Systems that generate messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consumers&lt;/strong&gt;: Systems that process messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brokers&lt;/strong&gt;: The middleware that stores and routes messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queues/Topics&lt;/strong&gt;: Named destinations for messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queues&lt;/strong&gt;: Where each message is consumed by a single recipient (point-to-point)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Topics/Exchanges&lt;/strong&gt;: Where messages can be broadcast to multiple subscribers (publish-subscribe)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When using a messaging system, the sender doesn't need to know about the recipient - it simply publishes a message and moves on. Also called &lt;em&gt;fire-and-forget&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strengths and Trade-offs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decoupling&lt;/strong&gt;: Services don't need to know about each other's location or implementation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resilience&lt;/strong&gt;: System can continue functioning even if some components are down&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buffering&lt;/strong&gt;: Great for elasticity, we can handle traffic spikes by queuing messages before consuming them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Trade-offs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Increased complexity&lt;/strong&gt;: Additional infrastructure to maintain and monitor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eventual consistency&lt;/strong&gt;: Messages are processed asynchronously, so data may be temporarily inconsistent, however, there are workarounds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging challenges&lt;/strong&gt;: Message flow can be harder to trace than direct calls, testing is difficult too&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ordering guarantees&lt;/strong&gt;: Most systems provide only limited message ordering guarantees&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some common messaging systems are RabbitMQ, Apache Kafka, ActiveMQ, and cloud offerings like AWS SQS/SNS, Google Pub/Sub, and Azure Service Bus.&lt;/p&gt;

</description>
      <category>restapi</category>
      <category>graphql</category>
      <category>eventdriven</category>
      <category>database</category>
    </item>
    <item>
      <title>ELI5: What exactly are ACID and BASE Transactions?</title>
      <dc:creator>Lukas Niessen</dc:creator>
      <pubDate>Sun, 18 May 2025 23:41:52 +0000</pubDate>
      <link>https://dev.to/lukasniessen/eli5-what-exactly-are-acid-and-base-transactions-1fbp</link>
      <guid>https://dev.to/lukasniessen/eli5-what-exactly-are-acid-and-base-transactions-1fbp</guid>
      <description>&lt;h1&gt;
  
  
  ELI5: What exactly are ACID and BASE Transactions?
&lt;/h1&gt;

&lt;p&gt;In this article, I will cover ACID and BASE transactions. First I give an easy ELI5 explanation and then a deeper dive. At the end, I show code examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is ACID, what is BASE?
&lt;/h2&gt;

&lt;p&gt;When we say a database supports &lt;em&gt;ACID&lt;/em&gt; or &lt;em&gt;BASE&lt;/em&gt;, we mean it supports &lt;em&gt;ACID transactions&lt;/em&gt; or &lt;em&gt;BASE transactions&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ACID
&lt;/h3&gt;

&lt;p&gt;An ACID transaction is simply writing to the DB, but with these guarantees;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write it &lt;em&gt;all or nothing&lt;/em&gt;; writing A but not B cannot happen.&lt;/li&gt;
&lt;li&gt;If someone else writes at the same time, make sure it still works properly.&lt;/li&gt;
&lt;li&gt;Make sure the write stays.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Concretely, ACID stands for:&lt;/p&gt;

&lt;p&gt;A = Atomicity = &lt;em&gt;all or nothing (point 1)&lt;/em&gt;&lt;br&gt;&lt;br&gt;
C = Consistency&lt;br&gt;&lt;br&gt;
I = Isolation = &lt;em&gt;parallel writes work fine (point 2)&lt;/em&gt;&lt;br&gt;&lt;br&gt;
D = Durability = &lt;em&gt;write should stay (point 3)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  BASE
&lt;/h3&gt;

&lt;p&gt;A BASE transaction is again simply writing to the DB, but with weaker guarantees. BASE lacks a clear definition. However, it stands for:&lt;/p&gt;

&lt;p&gt;BA = Basically available&lt;br&gt;&lt;br&gt;
S = Soft state&lt;br&gt;&lt;br&gt;
E = Eventual consistency.&lt;/p&gt;

&lt;p&gt;What these terms usually mean is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Basically available&lt;/em&gt; just means the system prioritizes availability (see CAP theorem later).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Soft state&lt;/em&gt; means the system's state might not be immediately consistent and may change over time without explicit updates. (Particularly across multiple nodes, that is, when we have partitioning or multiple DBs)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Eventual consistency&lt;/em&gt; means the system becomes consistent over time, that is, at least if we stop writing. Eventual consistency is the only clearly defined part of BASE.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Notes
&lt;/h3&gt;

&lt;p&gt;You surely noticed I didn't address the C in ACID: consistency. It means that data follows the application's rules (invariants). In other words, if a transaction starts with valid data and preserves these rules, the data stays valid. But this is the not the database's responsibility, it's the application's. Atomicity, isolation, and durability are database properties, but consistency depends on the application. So the C doesn't really belong in ACID. Some argue the C was added to ACID to make the acronym work.&lt;/p&gt;

&lt;p&gt;The name ACID was coined in 1983 by Theo Härder and Andreas Reuter. The intent was to establish clear terminology for fault-tolerance in databases. However, how we get ACID, that is ACID transactions, is up to each DB. For example PostgreSQL implements ACID in a different way than MySQL - and surely different than MongoDB (which also supports ACID). Unfortunately when a system claims to support ACID, it's therefore not fully clear which guarantees they actually bring because ACID has become a marketing term to a degree.&lt;/p&gt;

&lt;p&gt;And, as you saw, BASE certainly has a very unprecise definition. One can say BASE means &lt;em&gt;Not-ACID&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Simple Examples
&lt;/h2&gt;

&lt;p&gt;Here quickly a few standard examples of why ACID is important.&lt;/p&gt;
&lt;h3&gt;
  
  
  Atomicity
&lt;/h3&gt;

&lt;p&gt;Imagine you're transferring $100 from your checking account to your savings account. This involves two operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Subtract $100 from checking&lt;/li&gt;
&lt;li&gt;Add $100 to savings&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Without transactions, if your bank's system crashes after step 1 but before step 2, you'd lose $100! With transactions, either both steps happen or neither happens. All or nothing - atomicity.&lt;/p&gt;
&lt;h3&gt;
  
  
  Isolation
&lt;/h3&gt;

&lt;p&gt;Suppose two people are booking the last available seat on a flight at the same time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alice sees the seat is available and starts booking.&lt;/li&gt;
&lt;li&gt;Bob also sees the seat is available and starts booking at the same time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without proper isolation, both transactions might think the seat is available and both might be allowed to book it—resulting in overbooking. With isolation, only one transaction can proceed at a time, ensuring data consistency and avoiding conflicts.&lt;/p&gt;
&lt;h3&gt;
  
  
  Durability
&lt;/h3&gt;

&lt;p&gt;Imagine you've just completed a large online purchase and the system confirms your order.&lt;/p&gt;

&lt;p&gt;Right after confirmation, the server crashes.&lt;/p&gt;

&lt;p&gt;Without durability, the system might "forget" your order when it restarts. With durability, once a transaction is committed (your order is confirmed), the result is permanent—even in the event of a crash or power loss.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code Snippet
&lt;/h2&gt;

&lt;p&gt;A transaction might look like the following. Everything between &lt;code&gt;BEGIN TRANSACTION&lt;/code&gt; and &lt;code&gt;COMMIT&lt;/code&gt; is considered part of the transaction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Subtract $100 from checking account&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;account_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'checking'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Add $100 to savings account&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;account_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'savings'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Ensure the account balances remain valid (Consistency)&lt;/span&gt;
&lt;span class="c1"&gt;-- Check if checking account balance is non-negative&lt;/span&gt;
&lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;account_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'checking'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
        &lt;span class="n"&gt;RAISE&lt;/span&gt; &lt;span class="n"&gt;EXCEPTION&lt;/span&gt; &lt;span class="s1"&gt;'Insufficient funds in checking account'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  COMMIT and ROLLBACK
&lt;/h2&gt;

&lt;p&gt;Two essential commands that make ACID transactions possible are COMMIT and ROLLBACK:&lt;/p&gt;

&lt;h3&gt;
  
  
  COMMIT
&lt;/h3&gt;

&lt;p&gt;When you issue a COMMIT command, it tells the database that all operations in the current transaction should be made permanent. Once committed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changes become visible to other transactions&lt;/li&gt;
&lt;li&gt;The transaction cannot be undone&lt;/li&gt;
&lt;li&gt;The database guarantees durability of these changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A COMMIT represents the successful completion of a transaction.&lt;/p&gt;

&lt;h3&gt;
  
  
  ROLLBACK
&lt;/h3&gt;

&lt;p&gt;When you issue a ROLLBACK command, it tells the database to discard all operations performed in the current transaction. This is useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An error occurs during the transaction&lt;/li&gt;
&lt;li&gt;Application logic determines the transaction should not complete&lt;/li&gt;
&lt;li&gt;You want to test operations without making permanent changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ROLLBACK ensures atomicity by preventing partial changes from being applied when something goes wrong.&lt;/p&gt;

&lt;p&gt;Example with ROLLBACK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;account_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'checking'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Check if balance is now negative&lt;/span&gt;
&lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;account_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'checking'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
    &lt;span class="c1"&gt;-- Insufficient funds, cancel the transaction&lt;/span&gt;
    &lt;span class="k"&gt;ROLLBACK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;-- Transaction is aborted, no changes are made&lt;/span&gt;
&lt;span class="k"&gt;ELSE&lt;/span&gt;
    &lt;span class="c1"&gt;-- Add the amount to savings&lt;/span&gt;
    &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;account_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'savings'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;-- Complete the transaction&lt;/span&gt;
    &lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why BASE?
&lt;/h2&gt;

&lt;p&gt;BASE used to be important because many DBs, for example document-oriented DBs, did not support ACID. They had other advantages. Nowadays however, most document-oriented DBs support ACID.&lt;/p&gt;

&lt;p&gt;So why even have BASE?&lt;/p&gt;

&lt;p&gt;ACID can get really difficult when having distributed DBs. For example when you have partitioning or you have a microservice architecture where each service has its own DB. If your transaction only writes to one partition (or DB), then there's no problem. But what if you have a transaction that spans accross multiple partitions or DBs, a so called &lt;em&gt;distributed transaction&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;The short answer is: we either work around it or we loosen our guarantees from ACID to ... BASE.&lt;/p&gt;

&lt;h2&gt;
  
  
  ACID in Distributed Databases
&lt;/h2&gt;

&lt;p&gt;Let's address ACID one by one. Let's only consider partitioned DBs for now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Atomicity
&lt;/h3&gt;

&lt;p&gt;Difficult. If we do a write on partition &lt;code&gt;A&lt;/code&gt; and it works but one on &lt;code&gt;B&lt;/code&gt; fails, we're in trouble.&lt;/p&gt;

&lt;h3&gt;
  
  
  Isolation
&lt;/h3&gt;

&lt;p&gt;Difficult. If we have multiple transactions concurrently access data across different partitions, it's hard to ensure isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Durability
&lt;/h3&gt;

&lt;p&gt;No problem since each node has durable storage.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about Microservice Architectures?
&lt;/h3&gt;

&lt;p&gt;Pretty much the same issues as with partitioned DBs. However, it gets even more difficult because microservices are independently developed and deployed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solutions
&lt;/h3&gt;

&lt;p&gt;There are two primary approaches to handling transactions in distributed systems:&lt;/p&gt;

&lt;h4&gt;
  
  
  Two-Phase Commit (2PC)
&lt;/h4&gt;

&lt;p&gt;Two-Phase Commit is a protocol designed to achieve atomicity in distributed transactions. It works as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prepare Phase&lt;/strong&gt;: A coordinator node asks all participant nodes if they're ready to commit&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Each node prepares the transaction but doesn't commit&lt;/li&gt;
&lt;li&gt;Nodes respond with "ready" or "abort"&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Commit Phase&lt;/strong&gt;: If all nodes are ready, the coordinator tells them to commit

&lt;ul&gt;
&lt;li&gt;If any node responded with "abort," all nodes are told to rollback&lt;/li&gt;
&lt;li&gt;If all nodes responded with "ready," all nodes are told to commit&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;2PC guarantees atomicity but has significant drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's blocking (participants must wait for coordinator decisions)&lt;/li&gt;
&lt;li&gt;Performance overhead due to multiple round trips&lt;/li&gt;
&lt;li&gt;Vulnerable to coordinator failures&lt;/li&gt;
&lt;li&gt;Can lead to extended resource locking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example of 2PC in pseudo-code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Coordinator
function twoPhaseCommit(transaction, participants) {
    // Phase 1: Prepare
    for each participant in participants {
        response = participant.prepare(transaction)
        if response != "ready" {
            for each participant in participants {
                participant.abort(transaction)
            }
            return "Transaction aborted"
        }
    }

    // Phase 2: Commit
    for each participant in participants {
        participant.commit(transaction)
    }
    return "Transaction committed"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Saga Pattern
&lt;/h4&gt;

&lt;p&gt;The Saga pattern is a sequence of local transactions where each transaction updates a single node. After each local transaction, it publishes an event that triggers the next transaction. If a transaction fails, compensating transactions are executed to undo previous changes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Forward transactions&lt;/strong&gt;: T1, T2, ..., Tn&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compensating transactions&lt;/strong&gt;: C1, C2, ..., Cn-1 (executed if something fails)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, an order processing flow might have these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create order&lt;/li&gt;
&lt;li&gt;Reserve inventory&lt;/li&gt;
&lt;li&gt;Process payment&lt;/li&gt;
&lt;li&gt;Ship order&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the payment fails, compensating transactions would:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cancel shipping&lt;/li&gt;
&lt;li&gt;Release inventory reservation&lt;/li&gt;
&lt;li&gt;Cancel order&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sagas can be implemented in two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Choreography&lt;/strong&gt;: Services communicate through events&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration&lt;/strong&gt;: A central coordinator manages the workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example of a Saga in pseudo-code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Orchestration approach
function orderSaga(orderData) {
    try {
        orderId = orderService.createOrder(orderData)
        inventoryId = inventoryService.reserveItems(orderData.items)
        paymentId = paymentService.processPayment(orderData.payment)
        shippingId = shippingService.scheduleDelivery(orderId)
        return "Order completed successfully"
    } catch (error) {
        if (shippingId) shippingService.cancelDelivery(shippingId)
        if (paymentId) paymentService.refundPayment(paymentId)
        if (inventoryId) inventoryService.releaseItems(inventoryId)
        if (orderId) orderService.cancelOrder(orderId)
        return "Order failed: " + error.message
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What about Replication?
&lt;/h2&gt;

&lt;p&gt;There are mainly three way of replicating your DB. Single-leader, multi-leader and leaderless. I will not address multi-leader.&lt;/p&gt;

&lt;h3&gt;
  
  
  Single-leader
&lt;/h3&gt;

&lt;p&gt;ACID is not a concern here. If the DB supports ACID, replicating it won't change anything. You write to the leader via an ACID transaction and the DB will make sure the followers are updated. Of course, when we have asynchronous replication, we don't have consistency. But this is not an ACID problem, it's a asynchronous replication problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leaderless Replication
&lt;/h3&gt;

&lt;p&gt;In leaderless replication systems (like Amazon's Dynamo or Apache Cassandra), ACID properties become more challenging to implement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Atomicity&lt;/strong&gt;: Usually limited to single-key operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt;: Often relaxed to eventual consistency (BASE)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolation&lt;/strong&gt;: Typically provides limited isolation guarantees&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durability&lt;/strong&gt;: Achieved through replication to multiple nodes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach prioritizes availability and partition tolerance over consistency, aligning with the BASE model rather than strict ACID.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;ACID provides strong guarantees but can be challenging to implement across distributed systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;BASE offers more flexibility but requires careful application design to handle eventual consistency&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's important to understand ACID vs BASE and the whys.&lt;/p&gt;

&lt;p&gt;The right choice depends on your specific requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Financial applications may need ACID guarantees&lt;/li&gt;
&lt;li&gt;Social media applications might work fine with BASE semantics (at least most parts of it).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>acid</category>
      <category>microservices</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>ELI5: Database Partitioning</title>
      <dc:creator>Lukas Niessen</dc:creator>
      <pubDate>Sun, 18 May 2025 15:30:43 +0000</pubDate>
      <link>https://dev.to/lukasniessen/eli5-database-partitioning-1nmh</link>
      <guid>https://dev.to/lukasniessen/eli5-database-partitioning-1nmh</guid>
      <description>&lt;h1&gt;
  
  
  ELI5: Database Partitioning
&lt;/h1&gt;

&lt;p&gt;This article is Database Partitioning in ELI5. Not only that though, I also cover each topic with a more thorough explanation. I will cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is partitioning?&lt;/li&gt;
&lt;li&gt;Why partition your database?&lt;/li&gt;
&lt;li&gt;Partitioning vs. Replication&lt;/li&gt;
&lt;li&gt;Key-Value partitioning strategies&lt;/li&gt;
&lt;li&gt;Handling skewed workloads and hot spots&lt;/li&gt;
&lt;li&gt;Secondary indexes with partitioning&lt;/li&gt;
&lt;li&gt;Request routing and service discovery&lt;/li&gt;
&lt;li&gt;Parallel query execution&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is Partitioning? ELI5
&lt;/h2&gt;

&lt;p&gt;Say we run Facebook. There are way too many posts to store it on one computer. So we split it, some posts here, some posts there. Each split is a &lt;em&gt;partition&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Partitioning = Splitting a database into smaller chunks across multiple machines&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first question is, of course, how do we partition? But there are other questions that need an answer. I will address them later.&lt;/p&gt;

&lt;p&gt;Note: There are other names for &lt;em&gt;partitions&lt;/em&gt;, for example, it's called &lt;em&gt;shards&lt;/em&gt; (&lt;em&gt;sharding&lt;/em&gt;) in MongoDB and Elasticsearch, or a &lt;em&gt;tablet&lt;/em&gt; with Bigtable. However, &lt;em&gt;partitioning&lt;/em&gt; is the most established term.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Partition? ELI5
&lt;/h2&gt;

&lt;p&gt;As said, the main reason for partitioning is &lt;strong&gt;scalability&lt;/strong&gt;. When your data or query load gets too big for a single machine to handle, you need to break it up.&lt;/p&gt;

&lt;p&gt;With partitioning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can store more data than fits on one machine&lt;/li&gt;
&lt;li&gt;You can distribute query load across many processors&lt;/li&gt;
&lt;li&gt;Different partitions can be placed on different nodes in a shared-nothing cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We call a DB holding a partition a &lt;em&gt;node&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This has other advantages as well, for example parallelizing queries. For queries that only need data from a single partition, each node can independently handle its part, so you can scale query throughput by adding more nodes. Complex queries that span partitions are harder but can potentially be parallelized.&lt;/p&gt;

&lt;h2&gt;
  
  
  Partitioning vs. Replication
&lt;/h2&gt;

&lt;p&gt;Partitioning is usually &lt;strong&gt;combined&lt;/strong&gt; with replication. That means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Partitioning splits the data into smaller subsets&lt;/li&gt;
&lt;li&gt;Each partition is then replicated on multiple nodes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even though each record belongs to only one partition, it may be stored on several different nodes for fault tolerance. A node may store more than one partition. In a leader-follower model, a node might be the leader for some partitions and a follower for others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key-Value Partitioning Strategies
&lt;/h2&gt;

&lt;p&gt;Alright, so let's address the first question. How do we decide which records go on which nodes?&lt;/p&gt;

&lt;p&gt;The goal is to spread data and query load evenly. If every node takes a fair share, then 5 nodes should theoretically handle 5 times as much data and throughput as one node.&lt;/p&gt;

&lt;p&gt;If the partitioning is uneven, so some partitions have more data or queries than others, we call it &lt;strong&gt;skewed&lt;/strong&gt;. An extreme case of skew is a &lt;strong&gt;hot spot&lt;/strong&gt;, so in other words, a partition with disproportionately high load.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Partitioning by Key Range
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;: Assign each partition a continuous range of keys (from some minimum to some maximum).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; With movies, you could use the name's starting letters as the keys. So movies with A are stored on one node, those starting with B on the other, and so on.&lt;/p&gt;

&lt;p&gt;Partition boundaries can be chosen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manually by an administrator&lt;/li&gt;
&lt;li&gt;Automatically by the database&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach is used by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bigtable and HBase&lt;/li&gt;
&lt;li&gt;RethinkDB&lt;/li&gt;
&lt;li&gt;MongoDB (before version 2.4)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Within each partition, keys are kept in sorted order. This makes range scans efficient and enables treating the key as a concatenated index for fetching related records in one query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: Sensor network data where the key is a timestamp. You can easily fetch all readings from a particular month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Certain access patterns create hot spots. If the key is a timestamp, all writes go to the partition for "today" while other partitions sit idle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Use something other than a timestamp as the first element of the key. For example, prefix each timestamp with the sensor name so partitioning happens first by sensor, then by time. This spreads write load across partitions.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Partitioning by Hash of Key
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;: Apply a hash function to keys and assign each partition a range of hash values.&lt;/p&gt;

&lt;p&gt;A good hash function takes skewed data and makes it uniformly distributed. For example, Cassandra and MongoDB use this (with MD5).&lt;/p&gt;

&lt;p&gt;This approach distributes keys fairly among partitions. The partition boundaries can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Evenly spaced&lt;/li&gt;
&lt;li&gt;Chosen pseudorandomly (sometimes called consistent hashing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Big Tradeoff&lt;/strong&gt;: By using a hash of the key, we lose the ability to do efficient range queries. Keys that were once adjacent are now scattered across partitions, and their sort order is lost.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In MongoDB with hash-based sharding, range queries must be sent to all partitions&lt;/li&gt;
&lt;li&gt;Range queries on the primary key are not supported in Riak, Couchbase, or Voldemort&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Handling Skewed Workloads and Hot Spots
&lt;/h2&gt;

&lt;p&gt;Hashing helps reduce hot spots but can't eliminate them entirely. If all reads and writes target the same key, all requests still go to the same partition.&lt;/p&gt;

&lt;p&gt;This happens in real life: a celebrity on social media doing something noteworthy can create a storm of activity on a single key (the celebrity's user ID or the ID of the action people are commenting on). Hashing doesn't help because identical IDs hash to the same value.&lt;/p&gt;

&lt;p&gt;There are solutions for this too though, with tradeoffs of course.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request Routing and Service Discovery
&lt;/h2&gt;

&lt;p&gt;Now that we've partitioned our dataset across multiple nodes. Great.&lt;/p&gt;

&lt;p&gt;But there's an issue not addressed yet. When a client wants to make a request, how does it know which node to connect to? For example which IP address should it connect to?&lt;/p&gt;

&lt;p&gt;This problem is generally (also outside of DBs) known as &lt;em&gt;service discovery&lt;/em&gt;. There are the main approaches:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Routing Tier
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;All client requests go through a routing layer first&lt;/li&gt;
&lt;li&gt;This layer determines the right node for each request and forwards accordingly&lt;/li&gt;
&lt;li&gt;The routing tier is essentially a partition-aware load balancer&lt;/li&gt;
&lt;li&gt;It doesn't handle requests itself&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Any-Node Routing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Clients can contact any node (for example via a round-robin load balancer)&lt;/li&gt;
&lt;li&gt;If that node has the partition for the request, it handles it directly&lt;/li&gt;
&lt;li&gt;Otherwise, it forwards the request to the appropriate node and passes the reply back&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Client-Aware Routing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Clients know about the partitioning scheme and partition-to-node mapping&lt;/li&gt;
&lt;li&gt;They connect directly to the appropriate node without intermediaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many systems rely on a separate coordination service like ZooKeeper to track cluster metadata:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Each node registers in ZooKeeper&lt;/li&gt;
&lt;li&gt;ZooKeeper maintains the authoritative partition-to-node mapping&lt;/li&gt;
&lt;li&gt;Routing tiers or clients subscribe to this information&lt;/li&gt;
&lt;li&gt;When partitions change ownership, ZooKeeper notifies subscribers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LinkedIn's Espresso uses Helix (built on ZooKeeper)&lt;/li&gt;
&lt;li&gt;HBase, SolrCloud, and Kafka use ZooKeeper directly&lt;/li&gt;
&lt;li&gt;MongoDB uses its own config server implementation with mongos daemons as the routing tier&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Request Routing Works with ZooKeeper
&lt;/h2&gt;

&lt;p&gt;So, you've partitioned your dataset across multiple nodes, and you're using a coordination service like ZooKeeper to manage cluster metadata. But how does a client actually get its request to the right node? Let's break down the flow, focusing on a system with a &lt;strong&gt;routing tier&lt;/strong&gt; and ZooKeeper.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Request Routing Flow
&lt;/h2&gt;

&lt;p&gt;Here's how it typically works when a client makes a request in a distributed system with a routing tier and ZooKeeper:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Client Sends Request to Routing Tier&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The client doesn't know which node holds the data it needs, so it sends its request (e.g., a database query) to a &lt;strong&gt;routing tier&lt;/strong&gt;. This is a partition-aware load balancer, like MongoDB's &lt;code&gt;mongos&lt;/code&gt; daemon or a custom proxy. The routing tier's job is to figure out where to send the request.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Routing Tier Consults ZooKeeper&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The routing tier needs to know which node owns the partition for the requested data. It queries &lt;strong&gt;ZooKeeper&lt;/strong&gt;, which maintains the authoritative &lt;strong&gt;partition-to-node mapping&lt;/strong&gt;. ZooKeeper stores this metadata in a hierarchical structure (like a file system), updated whenever nodes join, leave, or partitions are reassigned. The routing tier either:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Caches this mapping and subscribes to ZooKeeper for updates (to stay current), or&lt;/li&gt;
&lt;li&gt;Queries ZooKeeper on-demand for each request (less common due to latency).&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ZooKeeper Provides Metadata&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
ZooKeeper responds with the current partition-to-node mapping. For example, it might say, "Partition P1 is on Node A (IP: 192.168.1.10), Partition P2 is on Node B (IP: 192.168.1.11)." This tells the routing tier exactly where to send the request.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Routing Tier Forwards the Request&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Armed with the mapping, the routing tier forwards the client's request to the correct node (e.g., Node A). The node processes the request, interacts with the database, and returns the response to the routing tier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Routing Tier Returns Response to Client&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The routing tier passes the response back to the client, completing the request. From the client's perspective, it just sent a request and got a response, unaware of the coordination happening behind the scenes.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>database</category>
      <category>programming</category>
    </item>
    <item>
      <title>ELI5: Database Replication</title>
      <dc:creator>Lukas Niessen</dc:creator>
      <pubDate>Sun, 18 May 2025 13:50:51 +0000</pubDate>
      <link>https://dev.to/lukasniessen/eli5-database-replication-14f</link>
      <guid>https://dev.to/lukasniessen/eli5-database-replication-14f</guid>
      <description>&lt;p&gt;This article is Database Replication in ELI5. Not only though, I also cover each topic with a more thorugh summary. I will cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is replication?&lt;/li&gt;
&lt;li&gt;Why replication?&lt;/li&gt;
&lt;li&gt;Leader-based, multi-leader, and leaderless&lt;/li&gt;
&lt;li&gt;Synchronous vs. asynchronous replication&lt;/li&gt;
&lt;li&gt;How to handle node failures&lt;/li&gt;
&lt;li&gt;Problems with replication lag&lt;/li&gt;
&lt;li&gt;Setting up new replicas&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is Replication? ELI5
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Replication = Keeping copies of the same data on multiple machines&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Replication? ELI5
&lt;/h2&gt;

&lt;p&gt;Three key reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latency&lt;/strong&gt;: Keep data geographically close to users

&lt;ul&gt;
&lt;li&gt;Let's say you're in China. Your app will use a nearby database server, ideally also in China. This is much faster than using one in the US for example.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Availability&lt;/strong&gt;: Keep the system running even when some parts fail

&lt;ul&gt;
&lt;li&gt;What if a DB server goes down? We will just connect to other, more to this later.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Read throughput&lt;/strong&gt;: Scale out machines serving read queries

&lt;ul&gt;
&lt;li&gt;If you have many users, it's better to have more than one server. Imagine YouTube serving all content from a single computer - not possible. So they put the content on multiple machines and distribute serving content among them.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where's the Challenge?
&lt;/h2&gt;

&lt;p&gt;So this was super easy, but of course real life is not that easy. There are many difficulties in replicating your data. Too many to cover in this article so I will just cover the most important ones, the ones you should know about.&lt;/p&gt;

&lt;p&gt;First off, if you have 5,000 Terrabytes of data, replicating all that data is probably too much. So you would also want to split the data between DBs. This is called &lt;em&gt;partitioning&lt;/em&gt; and is on purpose &lt;em&gt;ignored&lt;/em&gt; here. See my other article for that.&lt;/p&gt;

&lt;p&gt;So, the main challenge with replication is handling changes. It's easy to once copy data to 10 different computers. But what do we do when we get new changes? There are 3 main approaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leader-Based Replication
&lt;/h2&gt;

&lt;p&gt;The most common approach is the &lt;em&gt;leader-based model&lt;/em&gt; (also called &lt;em&gt;master-slave&lt;/em&gt;). Here's how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One &lt;em&gt;node&lt;/em&gt; (computer with DB) is the &lt;em&gt;leader&lt;/em&gt; (or &lt;em&gt;master&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;The other nodes are called &lt;em&gt;followers&lt;/em&gt; (or &lt;em&gt;slaves&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Clients send write requests only to the leader

&lt;ul&gt;
&lt;li&gt;Followers are read only!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The leader writes to its local storage and sends changes to the followers&lt;/li&gt;
&lt;li&gt;Followers apply these changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a high level overview skipping over details. There are still some key questions left, even on this high level.&lt;/p&gt;

&lt;h3&gt;
  
  
  Synchronous vs. Asynchronous Replication
&lt;/h3&gt;

&lt;p&gt;A critical decision in replication design is: should changes be applied synchronously or asynchronously?&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;synchronous replication&lt;/strong&gt;, the leader waits for the follower to confirm it received the write before confirming success to the client. This guarantees the follower's data is up-to-date with the leader.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;asynchronous replication&lt;/strong&gt;, the leader doesn't wait for acknowledgment from followers. It processes the write locally and moves on, followers catch up when they can.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Trade-offs:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Synchronous&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Guarantees up-to-date copies&lt;/li&gt;
&lt;li&gt;Guarantees durability (that is, we know writes are acutally &lt;em&gt;'successful'&lt;/em&gt;, that is persisted)&lt;/li&gt;
&lt;li&gt;But it also means we are slower&lt;/li&gt;
&lt;li&gt;For example, just one slow follower means the whole system needs to wait. Other writes are blocked.&lt;/li&gt;
&lt;li&gt;As communication over the network is unreliable, this is a big tradeoff.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Asynchronous&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Better performance&lt;/li&gt;
&lt;li&gt;Writes are not blocked, despite any network conditions or other factors&lt;/li&gt;
&lt;li&gt;However, followers might lag behind (can be up to minutes)&lt;/li&gt;
&lt;li&gt;So we do not have &lt;em&gt;consistency&lt;/em&gt; anymore, that is, you get result A from one DB but result B from another.&lt;/li&gt;
&lt;li&gt;Furthermore, what if a write totally fails because a follower is down for example? Then the write is not persisted.&lt;/li&gt;
&lt;li&gt;So we don't have ensured &lt;em&gt;durability&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;So both have big tradeoffs. Some systems use &lt;em&gt;semi-synchronous&lt;/em&gt; replication: one follower is synchronous, the rest are asynchronous. This guarantees at least two nodes have the latest data without sacrificing too much performance.&lt;/p&gt;

&lt;p&gt;However, most distributed systems use fully asynchronous replication. This is a conscious trade-off of durability for availability and performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up New Followers
&lt;/h2&gt;

&lt;p&gt;Sometimes you need new followers - maybe to increase read capacity or replace failed nodes. How do you set this up without downtime?&lt;/p&gt;

&lt;p&gt;The conceptual process works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take a consistent snapshot of the leader's database&lt;/li&gt;
&lt;li&gt;Copy the snapshot to the new follower&lt;/li&gt;
&lt;li&gt;The follower connects to the leader and requests all changes since the snapshot&lt;/li&gt;
&lt;li&gt;When the follower processes the backlog, it has "caught up" and can continue applying changes in real-time&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This process varies significantly between database systems. Some automate it fully, while others require manual administrator intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Node Outages
&lt;/h2&gt;

&lt;p&gt;Nodes fail. It's inevitable. Good replication systems should handle these failures. Fault tolerance is one of the main reasons for replications.&lt;/p&gt;

&lt;h3&gt;
  
  
  When a Follower Fails: Catch-up Recovery
&lt;/h3&gt;

&lt;p&gt;This one is straightforward. When a follower recovers from a crash, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Checks its local &lt;em&gt;log&lt;/em&gt; to find the last &lt;em&gt;transaction&lt;/em&gt; it processed&lt;/li&gt;
&lt;li&gt;Connects to the leader and requests all changes since that point&lt;/li&gt;
&lt;li&gt;Applies these changes to catch up&lt;/li&gt;
&lt;li&gt;Resumes normal operation&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  When a Leader Fails: Failover
&lt;/h3&gt;

&lt;p&gt;This is a bit trickier. When a leader fails, we need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detect the failure (usually via timeout)&lt;/li&gt;
&lt;li&gt;Choose a new leader (usually the follower with the most up-to-date data)&lt;/li&gt;
&lt;li&gt;Reconfigure the system to use the new leader&lt;/li&gt;
&lt;li&gt;Handle client redirects to the new leader&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This process is called &lt;em&gt;failover&lt;/em&gt; and can be automatic or manual. But failover has issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With asynchronous replication, the new leader might be missing writes the old leader confirmed&lt;/li&gt;
&lt;li&gt;Split-brain scenario: two nodes both think they're the leader&lt;/li&gt;
&lt;li&gt;Setting the right timeout is hard - too short causes unnecessary failovers during temporary slowdowns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't just theoretical concerns. A prominent example was GitHub: they had an incident where an out-of-date MySQL follower was promoted to leader. The database used auto-incrementing IDs, and the new leader reused primary keys that were previously assigned, causing inconsistency with their Redis store and exposing private data to the wrong users. Read their blog for more: &lt;a href="https://github.blog/news-insights/company-news/oct21-post-incident-analysis/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For these reasons, some teams prefer to manually trigger failovers, accepting a brief outage instead of risking data corruption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replication Lag
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Replication Lag = replications (for example followers) are lagging behind the most recent data&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In normal operation, this lag might be milliseconds, but during heavy load or network issues, it can grow to seconds or even minutes. This introduces inconsistencies - the leader has newer data than followers.&lt;/p&gt;

&lt;p&gt;This isn't just theoretical. Some real-world issues caused by replication lag include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read-after-write inconsistency: A user writes something, then immediately tries to read it but gets directed to a follower that hasn't received the update yet&lt;/li&gt;
&lt;li&gt;Monotonic reads violations: A user sees newer data, then older data in subsequent reads&lt;/li&gt;
&lt;li&gt;Consistent prefix issues: Related updates appear in a confusing order&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Solutions for Replication Lag
&lt;/h3&gt;

&lt;p&gt;There are several approaches to address these issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read-your-writes consistency&lt;/strong&gt;: After writing, ensure subsequent reads go to the leader or only to up-to-date followers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monotonic reads&lt;/strong&gt;: Make sure each user always reads from the same replica&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent prefix reads&lt;/strong&gt;: Make sure causally related writes are seen in the correct order&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Implementing these in application code is complex and error-prone. Ideally, developers shouldn't have to worry about these issues - that's why transactions exist. Transactions are not covered in this article though.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Leader Replication
&lt;/h2&gt;

&lt;p&gt;The single-leader model has a critical weakness: if you can't reach the leader, you can't write to the database.&lt;/p&gt;

&lt;p&gt;Multi-leader replication addresses this by allowing multiple nodes to accept writes. Each write is still forwarded to all nodes. This approach is especially useful in scenarios like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-datacenter operation (a leader in each datacenter)&lt;/li&gt;
&lt;li&gt;Clients with offline operation (like calendar apps)&lt;/li&gt;
&lt;li&gt;Collaborative editing systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main challenge is handling write conflicts when different leaders accept conflicting changes to the same data.&lt;/p&gt;

&lt;p&gt;This is less common than single-leader replication and I will not go into detail here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leaderless Replication
&lt;/h2&gt;

&lt;p&gt;This is a totally different approach. In leaderless systems (sometimes called Dynamo-style after Amazon's system), any replica can directly accept writes from clients. There are no leaders.&lt;/p&gt;

&lt;p&gt;The typical approach works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The client sends writes to multiple replicas&lt;/li&gt;
&lt;li&gt;If enough replicas acknowledge the write, it's considered successful&lt;/li&gt;
&lt;li&gt;During reads, the client queries multiple replicas in parallel&lt;/li&gt;
&lt;li&gt;Version numbers identify the most recent value&lt;/li&gt;
&lt;li&gt;"Read repair" or anti-entropy processes fix stale data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This design eliminates the need for failover, making the system more resilient to node failures. Cassandra, Riak, and Amazon's DynamoDB use variations of this approach.&lt;/p&gt;

&lt;p&gt;I will also not go into detail here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Right Replication Model
&lt;/h2&gt;

&lt;p&gt;Each replication approach has its place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single-leader&lt;/strong&gt;: Simple, well-understood, works for most applications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-leader&lt;/strong&gt;: Good for multi-datacenter operation and offline clients&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leaderless&lt;/strong&gt;: Highly available for write-intensive workloads with weaker consistency needs&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>database</category>
      <category>programming</category>
      <category>data</category>
    </item>
    <item>
      <title>Relational vs Document-Oriented Database for Software Architecture</title>
      <dc:creator>Lukas Niessen</dc:creator>
      <pubDate>Sun, 18 May 2025 08:36:52 +0000</pubDate>
      <link>https://dev.to/lukasniessen/relational-vs-document-oriented-database-for-software-architecture-28fg</link>
      <guid>https://dev.to/lukasniessen/relational-vs-document-oriented-database-for-software-architecture-28fg</guid>
      <description>&lt;h1&gt;
  
  
  Relational vs Document-Oriented Database for Software Architecture
&lt;/h1&gt;

&lt;p&gt;What I go through in here is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Super quick refresher of what these two are&lt;/li&gt;
&lt;li&gt;Key differences&lt;/li&gt;
&lt;li&gt;Strengths and weaknesses&lt;/li&gt;
&lt;li&gt;System design examples (+ Spring Java code)&lt;/li&gt;
&lt;li&gt;Brief history&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the examples, I choose a relational DB in the first, and a document-oriented DB in the other. The focus is on &lt;em&gt;why&lt;/em&gt; did I make that choice. I also provide some example code for both.&lt;/p&gt;

&lt;p&gt;In the strengths and weaknesses part, I discuss both what &lt;em&gt;used to be a strength/weakness&lt;/em&gt; and how it looks nowadays.&lt;/p&gt;

&lt;h2&gt;
  
  
  Super short summary
&lt;/h2&gt;

&lt;p&gt;The two most common types of DBs are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Relational database (RDB)&lt;/strong&gt;: PostgreSQL, MySQL, MSSQL, Oracle DB, ...&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document-oriented database (document store):&lt;/strong&gt; MongoDB, DynamoDB, Cassandra, CouchDB...&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  RDB
&lt;/h3&gt;

&lt;p&gt;The key idea is: &lt;strong&gt;fit the data into a big table&lt;/strong&gt;. The columns are &lt;em&gt;properties&lt;/em&gt; and the rows are the &lt;em&gt;values&lt;/em&gt;. By doing this, we have our data in a very structured way. So we have much power for querying the data (using SQL). That is, we can do all sorts of filters, joints etc. The &lt;em&gt;way&lt;/em&gt; we arrange the data into the table is called the &lt;em&gt;database schema&lt;/em&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example table
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----+---------+---------------------+-----+
| ID | Name    | Email               | Age |
+----+---------+---------------------+-----+
| 1  | Alice   | alice@example.com   | 30  |
| 2  | Bob     | bob@example.com     | 25  |
| 3  | Charlie | charlie@example.com | 28  |
+----+---------+---------------------+-----+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A database can have many tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Document stores
&lt;/h3&gt;

&lt;p&gt;The key idea is: &lt;strong&gt;just store the data as it is&lt;/strong&gt;. Suppose we have an object. We just convert it to a JSON and store it as it is. We call this data a &lt;em&gt;document&lt;/em&gt;. It's not limited to JSON though, it can also be BSON (binary JSON) or XML for example.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example document
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"item"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Book"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;12.99&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"item"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.50&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each document is saved under a unique ID. This ID can be a path, for example in Google Cloud Firestore, but doesn't have to be.&lt;/p&gt;

&lt;p&gt;Many documents &lt;em&gt;'in the same bucket'&lt;/em&gt; is called a &lt;em&gt;collection&lt;/em&gt;. We can have many collections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Differences
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Schema
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;RDBs have a fixed schema. Every row &lt;em&gt;'has the same schema'&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Document stores don't have schemas. Each document can &lt;em&gt;'have a different schema'&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Data Structure
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;RDBs break data into normalized tables with relationships through foreign keys&lt;/li&gt;
&lt;li&gt;Document stores nest related data directly within documents as embedded objects or arrays&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Query Language
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;RDBs use SQL, a standardized declarative language&lt;/li&gt;
&lt;li&gt;Document stores typically have their own query APIs

&lt;ul&gt;
&lt;li&gt;Nowadays, the common document stores support SQL-like queries too&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  Scaling Approach
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;RDBs traditionally scale vertically (bigger/better machines)

&lt;ul&gt;
&lt;li&gt;Nowadays, the most common RDBs offer horizontal scaling as well (eg. PostgeSQL)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Document stores are great for horizontal scaling (more machines)&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  Transaction Support
&lt;/h4&gt;

&lt;p&gt;ACID = availability, consistency, isolation, durability&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RDBs have mature ACID transaction support&lt;/li&gt;
&lt;li&gt;Document stores traditionally sacrificed ACID guarantees in favor of performance and availability

&lt;ul&gt;
&lt;li&gt;The most common document stores nowadays support ACID though (eg. MongoDB)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Strengths, weaknesses
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Relational Databases
&lt;/h3&gt;

&lt;p&gt;I want to repeat a few things here again that have changed. As noted, nowadays, most document stores support SQL and ACID. Likewise, most RDBs nowadays support horizontal scaling.&lt;/p&gt;

&lt;p&gt;However, let's look at ACID for example. While document stores support it, it's much more mature in RDBs. So if your app puts super high relevance on ACID, then probably RDBs are better. But if your app just needs basic ACID, both works well and this shouldn't be the deciding factor.&lt;/p&gt;

&lt;p&gt;For this reason, I have put these points, that are supported in both, in &lt;strong&gt;parentheses&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data Integrity&lt;/strong&gt;: Strong schema enforcement ensures data consistency&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;Complex Querying&lt;/strong&gt;: Great for complex joins and aggregations across multiple tables)&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;ACID&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schema&lt;/strong&gt;: While the schema was listed as a strength, it also is a weakness. Changing the schema requires migrations which can be painful&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object-Relational Impedance Mismatch&lt;/strong&gt;: Translating between application objects and relational tables adds complexity. Hibernate and other Object-relational mapping (ORM) frameworks help though.&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;Horizontal Scaling&lt;/strong&gt;: Supported but sharding is more complex as compared to document stores)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Initial Dev Speed&lt;/strong&gt;: Setting up schemas etc takes some time&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Document-Oriented Databases
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schema Flexibility&lt;/strong&gt;: Better for heterogeneous data structures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Throughput:&lt;/strong&gt; Supports high throughput, especially write throughput&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;Horizontal Scaling&lt;/strong&gt;: Horizontal scaling is easier, you can shard document-wise (document 1-1000 on computer A and 1000-2000 on computer B))&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance for Document-Based Access&lt;/strong&gt;: Retrieving or updating an entire document is very efficient&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-to-Many Relationships&lt;/strong&gt;: Superior in this regard. You don't need joins or other operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locality&lt;/strong&gt;: See below&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Initial Dev Speed&lt;/strong&gt;: Getting started is quicker due to the flexibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Complex Relationships&lt;/strong&gt;: Many-to-one and many-to-many relationships are difficult and often require denormalization or application-level joins&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Consistency&lt;/strong&gt;: More responsibility falls on application code to maintain data integrity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query Optimization&lt;/strong&gt;: Less mature optimization engines compared to relational systems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage Efficiency&lt;/strong&gt;: Potential data duplication increases storage requirements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locality&lt;/strong&gt;: See below&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Locality
&lt;/h3&gt;

&lt;p&gt;I have listed locality as a strength and a weakness of document stores. Here is what I mean with this.&lt;/p&gt;

&lt;p&gt;In document stores, cocuments are typically stored as a single, continuous string, encoded in formats like JSON, XML, or binary variants such as MongoDB's BSON. This structure provides a locality advantage when applications need to access entire documents. Storing related data together minimizes disk seeks, unlike relational databases (RDBs) where data split across multiple tables - this requires multiple index lookups, increasing retrieval time.&lt;/p&gt;

&lt;p&gt;However, it's only a benefit when we need (almost) the entire document at once. Document stores typically load the entire document, even if only a small part is accessed. This is inefficient for large documents. Similarly, updates often require rewriting the entire document. So to keep these downsides small, make sure your documents are small.&lt;/p&gt;

&lt;p&gt;Last note: Locality isn't exclusive to document stores. For example Google Spanner or Oracle achieve a similar locality in a relational model.&lt;/p&gt;

&lt;h2&gt;
  
  
  System Design Examples
&lt;/h2&gt;

&lt;p&gt;Note that I limit the examples to the minimum so the article is not totally bloated. The code is incomplete on purpose. You can find the complete code in the examples folder of the repo.&lt;/p&gt;

&lt;p&gt;The examples folder contains two complete applications:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;financial-transaction-system&lt;/code&gt; - A Spring Boot and React application using a relational database (H2)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;content-management-system&lt;/code&gt; - A Spring Boot and React application using a document-oriented database (MongoDB)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each example has its own README file with instructions for running the applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1: Financial Transaction System
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Functional requirements
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Process payments and transfers&lt;/li&gt;
&lt;li&gt;Maintain accurate account balances&lt;/li&gt;
&lt;li&gt;Store audit trails for all operations&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Non-functional requirements
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Reliability (!!)&lt;/li&gt;
&lt;li&gt;Data consistency (!!)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Why Relational is Better Here
&lt;/h4&gt;

&lt;p&gt;We want reliability and data consistency. Though document stores support this too (ACID for example), they are less mature in this regard. The benefits of document stores are not interesting for us, so we go with an RDB.&lt;/p&gt;

&lt;p&gt;Note: If we would expand this example and add things like &lt;em&gt;profiles of sellers&lt;/em&gt;, &lt;em&gt;ratings&lt;/em&gt; and more, we might want to add a separate DB where we have different priorities such as availability and high throughput. With two separate DBs we can support different requirements and scale them independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Accounts:
- account_id (PK = Primary Key)
- customer_id (FK = Foreign Key)
- account_type
- balance
- created_at
- status

Transactions:
- transaction_id (PK)
- from_account_id (FK)
- to_account_id (FK)
- amount
- type
- status
- created_at
- reference_number
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Spring Boot Implementation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Entity classes&lt;/span&gt;
&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="nd"&gt;@Table&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"accounts"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IDENTITY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;accountId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;accountType&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;LocalDateTime&lt;/span&gt; &lt;span class="n"&gt;createdAt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Getters and setters&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="nd"&gt;@Table&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"transactions"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Transaction&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IDENTITY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;transactionId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@ManyToOne&lt;/span&gt;
    &lt;span class="nd"&gt;@JoinColumn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"from_account_id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="n"&gt;fromAccount&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@ManyToOne&lt;/span&gt;
    &lt;span class="nd"&gt;@JoinColumn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"to_account_id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="n"&gt;toAccount&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;LocalDateTime&lt;/span&gt; &lt;span class="n"&gt;createdAt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;referenceNumber&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Getters and setters&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Repository&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;TransactionRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JpaRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findByFromAccountAccountIdOrToAccountAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;accountId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;sameAccountId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findByCreatedAtBetween&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LocalDateTime&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LocalDateTime&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Service with transaction support&lt;/span&gt;
&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransferService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;AccountRepository&lt;/span&gt; &lt;span class="n"&gt;accountRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;TransactionRepository&lt;/span&gt; &lt;span class="n"&gt;transactionRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;TransferService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AccountRepository&lt;/span&gt; &lt;span class="n"&gt;accountRepository&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TransactionRepository&lt;/span&gt; &lt;span class="n"&gt;transactionRepository&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;accountRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;accountRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;transactionRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transactionRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Transactional&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Transaction&lt;/span&gt; &lt;span class="nf"&gt;transferFunds&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;fromAccountId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;toAccountId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="n"&gt;fromAccount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;accountRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fromAccountId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AccountNotFoundException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Source account not found"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="n"&gt;toAccount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;accountRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toAccountId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AccountNotFoundException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Destination account not found"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fromAccount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBalance&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;compareTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InsufficientFundsException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Insufficient funds in source account"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Update balances&lt;/span&gt;
        &lt;span class="n"&gt;fromAccount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setBalance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fromAccount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBalance&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;subtract&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;toAccount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setBalance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toAccount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBalance&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="n"&gt;accountRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fromAccount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;accountRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toAccount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Create transaction record&lt;/span&gt;
        &lt;span class="nc"&gt;Transaction&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setFromAccount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fromAccount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setToAccount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toAccount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAmount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TRANSFER"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"COMPLETED"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCreatedAt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LocalDateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setReferenceNumber&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generateReferenceNumber&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;transactionRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;generateReferenceNumber&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"TXN"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  System Design Example 2: Content Management System
&lt;/h2&gt;

&lt;p&gt;A content management system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Store various content types, including articles and products&lt;/li&gt;
&lt;li&gt;Allow adding new content types&lt;/li&gt;
&lt;li&gt;Support comments&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Non-functional requirements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Performance&lt;/li&gt;
&lt;li&gt;Availability&lt;/li&gt;
&lt;li&gt;Elasticity&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Document Store is Better Here
&lt;/h3&gt;

&lt;p&gt;As we have no critical transaction like in the previous example but are only interested in performance, availability and elasticity, document stores are a great choice. Considering that various content types is a requirement, our life is easier with document stores as they are schema-less.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Article&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;document&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"article123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"article"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Understanding NoSQL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jane Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jane@example.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lorem ipsum dolor sit amet..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nosql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tutorial"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"published"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"publishedDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-05-01T10:30:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"comment789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user101"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"userName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bob Johnson"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Great article!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-05-02T14:20:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"replies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reply456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"userName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jane Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Thanks Bob!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-05-02T15:45:00Z"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"viewCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"likeCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"featuredImage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/images/nosql-header.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"estimatedReadTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Product&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;document&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(completely&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;different&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;structure)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"product789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Premium Ergonomic Chair"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;299.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"categories"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"furniture"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"office"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ergonomic"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"variants"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"black"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EC-BLK-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"inStock"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gray"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EC-GRY-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"inStock"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"specifications"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"weight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"15kg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dimensions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"65x70x120cm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"material"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mesh and aluminum"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Spring Boot Implementation with MongoDB
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Document&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContentItem&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Common fields can be explicit&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;published&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt; &lt;span class="n"&gt;createdAt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt; &lt;span class="n"&gt;updatedAt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// The rest can be dynamic&lt;/span&gt;
    &lt;span class="nd"&gt;@DBRef&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Comment&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Basic getters and setters&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// MongoDB Repository&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ContentRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;MongoRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ContentItem&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ContentItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findByType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ContentItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findByTypeAndPublishedTrue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ContentItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findByData_TagsContaining&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Service for content management&lt;/span&gt;
&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContentService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ContentRepository&lt;/span&gt; &lt;span class="n"&gt;contentRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ContentService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ContentRepository&lt;/span&gt; &lt;span class="n"&gt;contentRepository&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contentRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contentRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ContentItem&lt;/span&gt; &lt;span class="nf"&gt;createContent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;ContentItem&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContentItem&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAuthor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCreatedAt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setUpdatedAt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPublished&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;contentRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ContentItem&lt;/span&gt; &lt;span class="nf"&gt;addComment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;contentId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Comment&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;ContentItem&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contentRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contentId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContentNotFoundException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content not found"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getComments&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setComments&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;());&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getComments&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setUpdatedAt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;contentRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Easily add new fields without migrations&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ContentItem&lt;/span&gt; &lt;span class="nf"&gt;addMetadata&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;contentId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;ContentItem&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contentRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contentId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContentNotFoundException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content not found"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getData&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Just update the field, no schema changes needed&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;contentRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Brief History of RDBs vs NoSQL
&lt;/h2&gt;

&lt;p&gt;RDBs originated from a revolutionizing paper of Edgar Codd in 1970. After a few years, RDBs dominated the world of DBs, mainly for their reliability and consistent structure.&lt;/p&gt;

&lt;p&gt;NoSQL emerged around 2009 (the term actually came from a Twitter hashtag for a meetup about non-relational databases) as companies like Google, Amazon, and Facebook developed custom solutions to handle their unprecedented scale. They published papers on their internal database systems, inspiring open-source alternatives like MongoDB, Cassandra, and Couchbase.&lt;/p&gt;

&lt;p&gt;The driving forces behind NoSQL adoption were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Need for horizontal scalability across many machines&lt;/li&gt;
&lt;li&gt;More flexible data models for rapidly evolving applications&lt;/li&gt;
&lt;li&gt;Performance optimization for specific query patterns&lt;/li&gt;
&lt;li&gt;Lower operational costs for massive datasets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As mentioned already, most of these driving forces are not supported by RDBs as well, so the hard distinctions between RDBs and document stores are blurring.&lt;/p&gt;

&lt;p&gt;Most modern databases incorporate features from both.&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>architecture</category>
      <category>database</category>
      <category>mongodb</category>
    </item>
    <item>
      <title>Data related Non-Functional Requirements</title>
      <dc:creator>Lukas Niessen</dc:creator>
      <pubDate>Fri, 16 May 2025 18:19:50 +0000</pubDate>
      <link>https://dev.to/lukasniessen/-data-related-non-functional-requirements-gi5</link>
      <guid>https://dev.to/lukasniessen/-data-related-non-functional-requirements-gi5</guid>
      <description>&lt;p&gt;Brief reminder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Functional requirements = what the system should do&lt;/li&gt;
&lt;li&gt;Non-functional requirements = how the system should behave&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Usually applications handle complex datasets and need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store data efficiently in databases&lt;/li&gt;
&lt;li&gt;Cache expensive operation results&lt;/li&gt;
&lt;li&gt;Provide search and filtering capabilities&lt;/li&gt;
&lt;li&gt;Process messages asynchronously&lt;/li&gt;
&lt;li&gt;and more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suppose you have a banking website. Would you rather show wrong numbers to your customers or show no data at all? Rather show no data. However, if you're Facebook, you would of course rather show wrong data (not-updated likes count for example) than no data.&lt;/p&gt;

&lt;p&gt;So non-functional requirements matter. Here I want to walk through the most important ones. However, I will only discuss on data related ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reliability
&lt;/h2&gt;

&lt;p&gt;Reliability simply means "continuing to work correctly, even when things go wrong."&lt;/p&gt;

&lt;p&gt;A reliable system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performs its function as expected,&lt;/li&gt;
&lt;li&gt;Tolerates user mistakes or other mistakes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Important distinction here: faults vs failures. A &lt;strong&gt;fault&lt;/strong&gt; is when one component doesn't work or works incorrectly, while a &lt;strong&gt;failure&lt;/strong&gt; is when the entire system stops working. We clearly want to focus on dealing with faults and prevent faults from causing failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hardware Faults
&lt;/h3&gt;

&lt;p&gt;Hardware faults will always occur. A disk will die after about 10-50 years (this is the Mean Time To Failure, or MTTF), someone can trips over a cable, power outages, and so on. In fact, in large data centers, disk failures happen daily.&lt;/p&gt;

&lt;p&gt;So we use redundancy. For example, instead of one disk, we use 4 disks for example (you can use RAID to combine them). Or instead of one computer at one location, we have 5 computers at 5 locations. When one is down, a different one takes over.&lt;/p&gt;

&lt;p&gt;This, by the way, has another advantage: rolling updates. With only one machine your website is down when you make updates, like a security patch at 4 am. With multiple machines, you update them one after another, and your website stays online the entire time.&lt;/p&gt;

&lt;p&gt;While it used to be enough to have redundancy on one machine, for example by combining disks using RAID, that is no longer the case for most applications. Cloud computing like AWS, Azure or GCP also play into this trend: it's common for machines you're code is running on to become unavailable, this is because AWS and the like are e designed to prioritize flexibility and elasticity. You will get a different machine instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Software Errors
&lt;/h3&gt;

&lt;p&gt;Also impossible to prevent. Developers make mistakes (ChatGPT &amp;amp; co even more). So we need good Quality Assurance (QA), including unit tests, integration tests, perhaps E2E tests, we need good monitoring, and more. But these errors will happen anyway.&lt;/p&gt;

&lt;h3&gt;
  
  
  Human Errors
&lt;/h3&gt;

&lt;p&gt;Humans do mistakes. There are studies showing that human error causes more outages than hardware failures actually. While this again is impossible to prevent, we generally want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design interfaces that make the right action obvious and wrong actions hard&lt;/li&gt;
&lt;li&gt;Detailed monitoring&lt;/li&gt;
&lt;li&gt;Enabling quick recovery (rollbacks, gradual deployment, data recomputation tools).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So we really want our system to be reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scalability
&lt;/h2&gt;

&lt;p&gt;Scalability is how well your system handles increased load. To understand it, you need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define your load parameters (requests per second, read/write ratio, etc.)&lt;/li&gt;
&lt;li&gt;Measure performance under different loads&lt;/li&gt;
&lt;li&gt;Implement solutions that maintain performance as load increases&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;This is another obvious one. Let's look at some metrics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Throughput:&lt;/strong&gt; The number of operations your system can handle per unit of time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency and Response Time:&lt;/strong&gt; Note, they are not the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latency:&lt;/strong&gt; The time it takes a request to reach its destination&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response Time:&lt;/strong&gt; The total time for a request to be processed and returned (includes latency plus processing time)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Response times are important. But when measuring them, it's important to mote are never constant. They vary for each request. We could take the average response time but that's not enough. We need to look the higher percentiles (that is the very high or very low response times).&lt;/p&gt;

&lt;p&gt;An example from Amazon makes very clear why. They found that a 100ms increase in response time reduces sales by 1%. Now they obviously don't want that, there is another reason why high percentiles are very important to them. The users with the worst response times were usually their best customers. That's because they have a long purchase history, slowing things down internally for actions related their account. But these users are the ones that bring the most money, so they really want to keep them happy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maintainability
&lt;/h2&gt;

&lt;p&gt;In many projects, it's more costly to keep things running than to initially develop them. Think of big legacy code systems for example. These systems are difficult to maintain.&lt;/p&gt;

&lt;p&gt;Maintainability has many aspects, here are a three important ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Operable:&lt;/strong&gt; Good operability means it's easy to keep the system running. Making the ops teams life easy. Some of their tasks are the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Managing deployments and configurations,&lt;/li&gt;
&lt;li&gt;Monitoring system health and fixing issues,&lt;/li&gt;
&lt;li&gt;Debugging problems,&lt;/li&gt;
&lt;li&gt;Keeping software and platforms patched and up-to-date,&lt;/li&gt;
&lt;li&gt;Capacity planning,&lt;/li&gt;
&lt;li&gt;Complex maintenance tasks (like platform migrations),&lt;/li&gt;
&lt;li&gt;and more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Simple:&lt;/strong&gt; Complex systems harder to understand, modify, and maintain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evolvable:&lt;/strong&gt; Systems should be easy to modify as requirements change. This requires good abstractions, clean interfaces, and proper separation of concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Others
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ACID
&lt;/h3&gt;

&lt;p&gt;Often you want ACID (Availability, Consistency, Isolation and Durability). This usually comes at a tradeoff though and is a topic for a separate article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Elasticity
&lt;/h2&gt;

&lt;p&gt;The ability to handle to handle bursts of requests. For example, during the Jake Paul vs Mike Tyson fight, the Netflix servers couldn't handle the traffic. While Netflix is very scalable, they couldn't handle this particular bursts of requests. They had &lt;em&gt;'bad elasticity'&lt;/em&gt; in this case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speed to Market
&lt;/h3&gt;

&lt;p&gt;In technology, speed to market often determines success. Some data teams deliberate on technology choices for months without making decisions, which can be fatal for success.&lt;/p&gt;

&lt;p&gt;Best practices include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delivering value early and often&lt;/li&gt;
&lt;li&gt;Using tools your team already knows when possible&lt;/li&gt;
&lt;li&gt;Avoiding undifferentiated heavy lifting that adds little value&lt;/li&gt;
&lt;li&gt;Selecting tools that enable quick, reliable, safe, and secure development&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Interoperability
&lt;/h3&gt;

&lt;p&gt;It's rare to use only one technology or system. Interoperability describes how various technologies connect, exchange information, and interact. When evaluating technologies, consider:&lt;/p&gt;

&lt;p&gt;How easily does technology A integrate with technology B?&lt;br&gt;
Is seamless integration already built into each product?&lt;br&gt;
How much manual configuration is needed?&lt;/p&gt;

&lt;h3&gt;
  
  
  And many more...
&lt;/h3&gt;

&lt;h2&gt;
  
  
  Balancing Trade-offs
&lt;/h2&gt;

&lt;p&gt;You will always have tradeoffs. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Higher reliability typically means higher costs&lt;/li&gt;
&lt;li&gt;Faster time to market might compromise long-term maintainability&lt;/li&gt;
&lt;li&gt;Better interoperability might require more standardized (but less optimized) approaches&lt;/li&gt;
&lt;li&gt;Lower initial costs might lead to higher long-term costs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember the first law of software architecture:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Everything in software architecture is a trade-off.&lt;/p&gt;
&lt;/blockquote&gt;

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