<?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: Romario Da Silva</title>
    <description>The latest articles on DEV Community by Romario Da Silva (@romario_dasilva_cbef4e6a).</description>
    <link>https://dev.to/romario_dasilva_cbef4e6a</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%2F3587573%2F72e8f683-56cc-4d4c-91cb-cb6dffacb4f5.png</url>
      <title>DEV Community: Romario Da Silva</title>
      <link>https://dev.to/romario_dasilva_cbef4e6a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/romario_dasilva_cbef4e6a"/>
    <language>en</language>
    <item>
      <title>Designing Data-Intensive Applications — Chapter 2: Data Models and Query Languages</title>
      <dc:creator>Romario Da Silva</dc:creator>
      <pubDate>Tue, 11 Nov 2025 22:15:55 +0000</pubDate>
      <link>https://dev.to/romario_dasilva_cbef4e6a/designing-data-intensive-applications-chapter-2-data-models-and-query-languages-4b2m</link>
      <guid>https://dev.to/romario_dasilva_cbef4e6a/designing-data-intensive-applications-chapter-2-data-models-and-query-languages-4b2m</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;This post is part of a series summarizing key ideas from Designing Data-Intensive Applications by Martin Kleppmann.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Layers of Abstraction
&lt;/h3&gt;

&lt;p&gt;Data Models work as the a sort of interface between different layers of abstractions.  &lt;/p&gt;

&lt;p&gt;Each model is an interface that enables every layer of abstraction to communicate with one another. &lt;/p&gt;

&lt;p&gt;Besides that, each model also works as a way to limit what each layer above can do. E.g. If you store your data in a relational database, that decision will come with limitations on what you can or should do with it.&lt;/p&gt;

&lt;p&gt;As applications developers, we model real world concepts into objects, classes, data structures. &lt;/p&gt;

&lt;p&gt;These objects are then modeled into tables, or JSON documents, or some other format, in order to be stored.&lt;/p&gt;

&lt;p&gt;Engineers that create those data storages systems need to think on a way to store that information in terms of bytes, in memory or disk. &lt;/p&gt;

&lt;p&gt;Hardware engineers come up with ways to represent those bytes with electrical currents.&lt;/p&gt;




&lt;h3&gt;
  
  
  Relational, Document, and Hybrid Models
&lt;/h3&gt;

&lt;p&gt;The relational model has clearly won the quest for the best way to model data. It’s been used since the 1970s. Despite that, in the 2010s, driven by the need for greater scalability, specialized query operations that aren’t well supported by the relational model, or simply more flexibility, NoSQL databases became popular.&lt;/p&gt;

&lt;p&gt;A common criticism of the SQL/relational model comes from the way most applications are written — using the OOP paradigm. In these cases, there’s a need for a translation layer between objects and tables. ORMs are commonly used to ease that process. This mismatch is often called the &lt;em&gt;impedance mismatch&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;JSON is often seen as a more natural alternative that solves the impedance mismatch problem. However, JSON comes with its own issues, which we’ll discuss later.&lt;/p&gt;

&lt;p&gt;When working with document databases, data locality is a big advantage. All the data is stored in one place, so you don’t need to look around in different tables. It’s important to note, though, that when you fetch a document, you’re fetching it entirely. So if you only need a small piece of information within that document, it might not be a good idea to store it as a document. For that reason, it’s generally recommended to use the document model for small objects.&lt;/p&gt;

&lt;p&gt;On the other hand, having IDs and foreign keys in relational databases can be extremely useful for many reasons. They make it easier to avoid ambiguity, simplify updates (you only need to update one place), and provide better search capabilities.&lt;/p&gt;

&lt;p&gt;Using an ID instead of plain text (relational model instead of document model) reduces data duplication. On top of that, since IDs don’t have real meaning for humans, they don’t need to change. Any piece of information that carries human meaning might need to change at some point. If you go for the document-based approach, you risk write overheads and inconsistencies while updating that information. Removing that duplication is what’s called &lt;em&gt;normalization&lt;/em&gt; in databases.&lt;/p&gt;

&lt;p&gt;To fetch normalized data, you need to perform joins across different tables. Document-based databases usually don’t have good support for joins. Also, data tends to get more and more interconnected as new features are added. In this sense, relational databases give us a stronger framework for dealing with that.&lt;/p&gt;

&lt;p&gt;Regarding schema flexibility, it’s inevitable that your data will take on some structure eventually; in that sense, the document model isn’t entirely &lt;em&gt;schemaless&lt;/em&gt;. The question is whether the database enforces that schema on write, or your application enforces it on read (&lt;em&gt;schema-on-read&lt;/em&gt;). Both approaches have trade-offs, and we can draw an interesting parallel here with dynamically vs. statically typed programming languages. The schema-on-read approach is usually helpful when you have very heterogeneous data.&lt;/p&gt;

&lt;p&gt;Modern databases implementing the relational model often include some form of storage locality, similar to document databases. Column-family databases like Cassandra are examples of that. It seems, though, that we’re moving toward a hybrid approach between the two.&lt;/p&gt;




&lt;h3&gt;
  
  
  Declarative vs Imperative Models and Historical Context
&lt;/h3&gt;

&lt;p&gt;The author illustrates the difference between imperative and declarative query languages. The key distinction is that, with a declarative approach, you specify only the result you want — not how to get it. This allows the database optimizer to perform its own optimizations whenever it deems necessary. Once again, the declarative approach can be compared to depending on an abstraction rather than an implementation.&lt;/p&gt;

&lt;p&gt;Returning to imperative vs. declarative query languages: since SQL (a relational model and declarative language) doesn’t let users enforce a specific algorithm or execution plan, query optimizers can perform optimizations that are often not possible with imperative languages. This also makes SQL better suited for distributed systems, where data is processed across multiple machines in parallel and controlling every detail of execution is difficult.&lt;/p&gt;

&lt;p&gt;In a way, the NoSQL debate is revisiting a discussion that computer science already faced in the 1970s. At that time, the most popular database system was IBM IMS, which implemented what was called the &lt;em&gt;hierarchical model&lt;/em&gt; — a model that shares many similarities with the JSON model used by document databases today. And, just like document databases, it struggled to represent many-to-one or many-to-many relationships.&lt;/p&gt;

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

&lt;p&gt;To address those limitations, two new models were widely discussed: the &lt;em&gt;network model&lt;/em&gt; and the &lt;em&gt;relational model.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The idea behind the network model was to expand on the hierarchical model. It kept the tree-like structure that worked well for one-to-many relationships but allowed a node to have more than one parent. The links between records worked similarly to pointers. To access a specific record, developers had to navigate through these links using what were called &lt;em&gt;access paths.&lt;/em&gt; In practice, this made the code for querying and updating databases quite complex and inflexible.&lt;/p&gt;

&lt;p&gt;The relational model, on the other hand, introduced the idea of query optimizers — systems capable of finding the most efficient way to retrieve data, or the best “access paths.” These optimizers are complex pieces of software, but they only need to be built once.&lt;/p&gt;

&lt;p&gt;This mirrors the difference between imperative and declarative query languages: while the network model required developers to know exactly how to reach a given record (imperative), the relational model allowed them to simply state what data they wanted (declarative).&lt;/p&gt;




&lt;h3&gt;
  
  
  Graph Databases and Complex Relationships
&lt;/h3&gt;

&lt;p&gt;Although relational databases can naturally model many-to-many relationships, when your needs for complex relationships grow, it’s often recommended to use a graph model instead (think of social networks or even Obsidian note links). Graphs can connect different types of objects as well.&lt;/p&gt;

&lt;p&gt;Graphs offer a more intuitive way to represent complex real-world relationships. To understand how they’re structured, we can think of them in terms of a relational database.&lt;/p&gt;

&lt;p&gt;We have &lt;strong&gt;vertices&lt;/strong&gt;, which contain an identifier and properties:&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;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;vertices&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;vertex_id&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;properties&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;strong&gt;edges&lt;/strong&gt;, which connect vertices. Every edge must have a tail vertex and a head vertex. Each edge represents a relationship identified by a label. Since every edge has both a head and a tail, there’s a sense of direction in the relationship.&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;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;edges&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;edge_id&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tail_vertex&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;vertices&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vertex_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;head_vertex&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;vertices&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vertex_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;properties&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s say we need to model which foods a person is allowed to consume. We could have vertices representing people, allergens, and foods. We could connect &lt;em&gt;Mark&lt;/em&gt; to &lt;em&gt;gluten&lt;/em&gt; with an edge labeled &lt;strong&gt;"allergic to"&lt;/strong&gt;, and &lt;em&gt;gluten&lt;/em&gt; to &lt;em&gt;bread&lt;/em&gt; with an edge labeled &lt;strong&gt;"found in"&lt;/strong&gt;. From there, we could infer that Mark is allergic to gluten found in bread.&lt;/p&gt;

&lt;p&gt;Now, imagine that in the future we’d like to add Mark’s address to the graph. We could simply create a &lt;em&gt;city&lt;/em&gt; vertex, say &lt;strong&gt;"London"&lt;/strong&gt;, and add an edge between &lt;em&gt;Mark&lt;/em&gt; and &lt;em&gt;London&lt;/em&gt; labeled &lt;strong&gt;"lives in"&lt;/strong&gt;. This flexibility — or &lt;em&gt;evolvability&lt;/em&gt; — is one of the key advantages of graph models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Neo4j&lt;/strong&gt; is a popular graph database that uses &lt;strong&gt;Cypher&lt;/strong&gt; as its declarative query language. Here’s an example of how to create a graph with Cypher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;NAmerica:&lt;/span&gt;&lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;name:&lt;/span&gt;&lt;span class="s1"&gt;'North America'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="py"&gt;type:&lt;/span&gt;&lt;span class="s1"&gt;'continent'&lt;/span&gt;&lt;span class="ss"&gt;}),&lt;/span&gt;
  &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;USA:&lt;/span&gt;&lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;name:&lt;/span&gt;&lt;span class="s1"&gt;'United States'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="py"&gt;type:&lt;/span&gt;&lt;span class="s1"&gt;'country'&lt;/span&gt;&lt;span class="ss"&gt;}),&lt;/span&gt;
  &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;Idaho:&lt;/span&gt;&lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;name:&lt;/span&gt;&lt;span class="s1"&gt;'Idaho'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="py"&gt;type:&lt;/span&gt;&lt;span class="s1"&gt;'state'&lt;/span&gt;&lt;span class="ss"&gt;}),&lt;/span&gt;
  &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;Lucy:&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;name:&lt;/span&gt;&lt;span class="s1"&gt;'Lucy'&lt;/span&gt;&lt;span class="ss"&gt;}),&lt;/span&gt;
  &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Idaho&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:WITHIN&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:WITHIN&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NAmerica&lt;/span&gt;&lt;span class="ss"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Lucy&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:BORN_IN&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Idaho&lt;/span&gt;&lt;span class="ss"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now suppose we want to find the names of all people who emigrated from the United States to Europe. To do that, we’d need to query people born in a city within the U.S. who currently live in Europe. Here’s how that query might look in Cypher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:BORN_IN&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:WITHIN&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mf"&gt;0.&lt;/span&gt;&lt;span class="n"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;us:&lt;/span&gt;&lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;name:&lt;/span&gt;&lt;span class="s1"&gt;'United States'&lt;/span&gt;&lt;span class="ss"&gt;}),&lt;/span&gt;
  &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:LIVES_IN&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:WITHIN&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mf"&gt;0.&lt;/span&gt;&lt;span class="n"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;eu:&lt;/span&gt;&lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;name:&lt;/span&gt;&lt;span class="s1"&gt;'Europe'&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;person.name&lt;/span&gt;&lt;span class="ss"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a structure like this, there are many possible ways to reach the same result. As is common with declarative query languages, the query optimizer decides the most efficient way to get there.&lt;/p&gt;

&lt;p&gt;You could achieve the same result using a relational database, but the query would need recursion, since you don’t know how many joins are required or how long you’d need to traverse the data before finding the answer. While it’s possible, it’s much more complicated to represent this kind of traversal with SQL.&lt;/p&gt;

&lt;p&gt;In the book’s example, the same query that takes &lt;strong&gt;4 lines&lt;/strong&gt; in Cypher requires &lt;strong&gt;29 lines&lt;/strong&gt; in SQL. It’s clear that some data models are simply better suited for certain use cases than others.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Designing Data-Intensive Applications — Chapter 1: Reliable, Scalable, and Maintainable Applications</title>
      <dc:creator>Romario Da Silva</dc:creator>
      <pubDate>Wed, 29 Oct 2025 20:29:59 +0000</pubDate>
      <link>https://dev.to/romario_dasilva_cbef4e6a/designing-data-intensive-applications-chapter-1-foundations-f8o</link>
      <guid>https://dev.to/romario_dasilva_cbef4e6a/designing-data-intensive-applications-chapter-1-foundations-f8o</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;This post is part of a series summarizing key ideas from Designing Data-Intensive Applications by Martin Kleppmann.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In most data-intensive applications, a few standard components appear repeatedly: databases, caches, search indexes, stream processors, and batch processors. Collectively, these are known as data systems.&lt;/p&gt;

&lt;p&gt;Each of these systems has its own characteristics, strengths, and weaknesses. To make informed decisions—like which database to use in a given scenario or which caching strategy fits best—we need to understand how these mechanisms work, what they excel at, and where they fall short.&lt;/p&gt;

&lt;p&gt;That’s where Designing Data-Intensive Applications by Martin Kleppmann comes in. The book’s goal is to establish a set of principles that help us make better design decisions. But before applying those principles, we first need to clarify what exactly we’re optimizing for with each choice.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Blurring Lines Between Data Systems
&lt;/h3&gt;

&lt;p&gt;Traditionally, different data systems had clearly defined roles. Databases, message queues, and caches each served distinct purposes. Today, however, those boundaries are much blurrier. Redis, for example, is often used as a messaging system through its publish/subscribe channels, while Kafka provides database-like durability guarantees.&lt;/p&gt;

&lt;p&gt;As developers, we now combine these once-specialized systems—each far more general-purpose than before—into composite systems that process data in specific ways to meet our business needs. In doing so, we essentially become data system designers ourselves.&lt;/p&gt;

&lt;p&gt;This shift means we face many of the same challenges that the creators of these systems encountered:&lt;br&gt;
How do we ensure data remains safe after a crash?&lt;br&gt;
How do we maintain consistency at the level our use case requires?&lt;br&gt;
In a sense, we’re solving the same kinds of problems—just at a different level of abstraction.&lt;/p&gt;




&lt;h3&gt;
  
  
  Reliability — Building from Unreliable Parts
&lt;/h3&gt;

&lt;p&gt;The book defines reliability not as having “reliable data,” but as building fault-tolerant systems—systems that continue to function correctly even when software, hardware, or human errors (or even malicious actions) occur.&lt;/p&gt;

&lt;p&gt;It’s important to distinguish between a fault and a failure.&lt;br&gt;
A fault happens when a component behaves unexpectedly.&lt;br&gt;
A failure occurs when that fault affects the entire system and users notice the problem.&lt;/p&gt;

&lt;p&gt;The goal of fault-tolerant design is to prevent faults from turning into failures.&lt;/p&gt;

&lt;p&gt;Since faults are inevitable, reliability comes from building reliable systems out of unreliable parts. One of the best ways to do this is by deliberately inducing faults to test your assumptions.&lt;/p&gt;

&lt;p&gt;Netflix’s “Chaos Monkey” is a great example. In their &lt;a href="//techblog.netflix.com/2011/07/netflix-simian-army.html"&gt;2011 blog post&lt;/a&gt;&lt;br&gt;
, they explain how they randomly terminate production servers to ensure systems can handle unexpected failures. The goal is to avoid the nightmare scenario: a real outage affecting customers while executives are on the call and engineers are troubleshooting under pressure for the first time. By creating controlled failures, Netflix can safely observe how their systems behave—and train teams to respond effectively when real faults occur.&lt;/p&gt;




&lt;h3&gt;
  
  
  Scalability — Understanding and Managing Load
&lt;/h3&gt;

&lt;p&gt;Next comes scalability. To scale a system, you first need to define what its load actually means. Is it the number of records processed per second? Active users? Requests per minute? You need this definition before deciding how to handle growth.&lt;/p&gt;

&lt;p&gt;Each system has load metrics that make more sense than others. Kleppmann uses Twitter as an example: reading tweets happens far more often than writing them. Users typically read dozens of tweets for every one they post.&lt;/p&gt;

&lt;p&gt;When someone tweets, Twitter must make that post appear on followers’ timelines within seconds. Because writes are less frequent, it makes sense to do most of the heavy lifting during the write—this makes reads much faster.&lt;/p&gt;

&lt;p&gt;To achieve this, Twitter fans out tweets to followers as they’re written, rather than making every read request fetch tweets from a single database. Otherwise, the database would quickly become a bottleneck. Of course, this approach has limits—the “celebrity problem” being one. Fanning out a single tweet to millions of followers is inefficient, so Twitter uses a different read workflow for those cases.&lt;/p&gt;




&lt;p&gt;Once you’ve defined your load, the next step is to understand how your system performs under it. A common metric here is response time (or latency).&lt;/p&gt;

&lt;p&gt;Response times are usually expressed as percentiles. For example, you might say that 95% of requests are processed in under 200 ms. The median (p50) means half of all requests complete within a given time. Amazon, for instance, monitors latency at the 99.9th percentile—because even if only 1 in 1,000 users experience slow responses, those users may represent high-value customers handling large transactions.&lt;/p&gt;

&lt;p&gt;At high percentiles, queuing delays often dominate. Servers can only process a limited number of requests in parallel, and a few slow ones can block the rest—this is called head-of-line blocking. Even fast requests end up waiting, making the entire system feel sluggish. For this reason, it’s crucial to measure response times from the client’s perspective, not just on the server.&lt;/p&gt;

&lt;p&gt;Once you have accurate measurements, you can plan how to handle increased load without sacrificing performance.&lt;/p&gt;

&lt;p&gt;The two main strategies are scaling up (using a more powerful machine) and scaling out (adding more machines). Scaling up is simpler but quickly becomes expensive. Scaling out is more complex—especially for stateful systems like databases—but it’s often the only practical path as demand grows.&lt;/p&gt;

&lt;p&gt;Fortunately, as distributed systems tools and abstractions improve, we can expect more data systems to be designed as distributed by default.&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>architecture</category>
      <category>dataengineering</category>
      <category>database</category>
    </item>
  </channel>
</rss>
