Skip to content
Vailati
Go back

DDIA 2nd edition, chapter 1: there are no solutions, only trade-offs

A summary of my reading of the chapter that builds the book’s vocabulary, and a parallel with a system I worked on.

This is a summary of my reading of chapter 1 of Designing Data-Intensive Applications, second edition, by Martin Kleppmann and Chris Riccomini (O’Reilly, 2025). The ideas belong to the authors. The selection, the order, the opinions and the parallel at the end are mine.

The chapter opens with a line from Thomas Sowell: “there are no solutions, there are only trade-offs”. It teaches no algorithm, no data structure, no protocol. It builds the mindset the authors expect from the reader: faced with a data architecture decision, the right question is not “which technology is best” but “what do I gain and what do I pay with each option, in my context”. The chapter organizes that into four tensions: operational versus analytical, cloud versus self-hosting, distributed versus single node, and business versus people’s rights.

What a data-intensive application is

An application is data-intensive when managing data is one of the main challenges of building it. The contrast is with compute-intensive applications, where the difficulty is parallelizing a huge calculation. In data-intensive ones the pain is elsewhere: volume, data that changes over time, consistency under failure and concurrency, high availability.

Almost nobody builds that from scratch. The application is assembled from standard building blocks: a database to store and find data again, a cache to remember the result of an expensive operation, a search index to filter by keyword, stream processing to react to events, batch processing to crunch accumulated volumes. The designer’s job is to pick those blocks and glue them with code. It gets hard when no single tool solves the problem and several have to be combined while keeping everything coherent.

The authors make a point early on: much of the confusion in data projects comes from different people wanting different things from the same data, without any of it being written down anywhere.

The book is about the backend, and it reminds us that application code is usually stateless: whatever has to survive between requests goes to the client or to the data infrastructure.

Operational versus analytical

The chapter starts with people. Backend engineers build services that read and change data. Analysts produce reports so management can decide. Data scientists look for patterns and build features on top of them. The last two do analytics: they look at data produced by others and, in general, do not modify it. That is the split that organizes the chapter.

Operational systems are where data is born; code reads and modifies it according to user actions. Analytical systems hold a read-only copy of that data, optimized for the kind of processing analysis requires.

The operational access pattern is OLTP: fetch a few records by key, write in response to the action of someone waiting for the answer. The analytical one is OLAP: scan a huge number of records and return aggregates.

PropertyOperational (OLTP)Analytical (OLAP)
Readsa few records by keyaggregation over many records
Writescreate, update and delete individual recordsbulk load or stream of events
Queriesfixed, embedded in the codearbitrary, ad hoc exploration
What the data representsthe current statethe history of events
Typical sizegigabytes to terabytesterabytes to petabytes

Adapted from Table 1-1 in the book.

OLTP users do not write free-form SQL, for security and performance; in analytics, free-form SQL is the rule. There is also a third category, real-time analytics: aggregations embedded in the product, with real-time ingestion and low-latency answers.

Data warehouse, data lake and what came next

The physical separation was born in the late 1980s. A large company has dozens or hundreds of OLTP systems, each with its own team. Querying them directly for analysis suffers from four problems: silos, the wrong schema for the job, impact on production, and security. The data warehouse is the read-only copy of all of them, fed by ETL: extract, transform, load.

Data scientists ask for what SQL serves poorly: feature engineering for models and unstructured data such as text and images. The answer was the data lake, a repository that keeps a copy of any useful data without imposing a file format, data model or schema, on top of cheap object storage. The lake became an intermediate stop between operations and the warehouse, holding raw data for each consumer to transform as needed. The authors call it the sushi principle: raw data is better.

Three movements complete the picture: governance weighs as much as the analysis itself; data arrives as events and stream processing answers in seconds; and there is a way back, reverse ETL, with a model trained in the lake going to production.

On HTAP, systems that promise OLTP and analytics in the same box: many are, inside, an OLTP system coupled to a separate analytical one, and they do not replace the warehouse, because each service has its own database and the company has a single warehouse to join them all. The larger the scale, the more specialized the systems become.

System of record and derived data

This is the most useful distinction in the chapter, in my reading.

The system of record holds the authoritative version. Every new piece of data is written there first; each fact appears once. If another system disagrees with it, the system of record is the one that is right. A derived data system holds what you get by taking data from somewhere else and transforming it: a cache, an index, a materialized view, denormalized data, even a trained ML model. If you lose derived data, you recreate it from the source.

flowchart LR
    IN([input]) -->|written first| SOR[(system of record)]
    SOR -->|derives| CACHE[(cache)]
    SOR -->|derives| IDX[(index)]
    SOR -->|derives| DW[(warehouse)]
    DW -->|trains| ML[model]
    CACHE -. lost? rebuild .-> SOR

Derived data is redundant, and it is what makes reads fast. Two fine points: analytical systems are almost always derived, while an operational service mixes both; and no database is by nature a system of record or a derived system, the role depends on how you use it. Being explicit about what derives from what untangles architectures. And when one piece of data derives from another, you need a process to propagate changes. Many databases were designed as if they were the only system in the application and do not help with that.

Cloud versus self-hosting

The rule of thumb for build or buy: what is a core competency stays in-house; the routine goes to a vendor. With software there are two separate decisions, who builds and who operates, and they form a spectrum: custom-built and operated by you; off the shelf on your hardware or on a VM; managed service; SaaS reached only through an API.

Using a cloud service is outsourcing operations. Vendors say that saves money; the authors answer “it depends”. Self-hosting tends to be cheaper when you already know how to run the system and the load is predictable. The cloud tends to win when you need something you do not know how to operate, when the load varies a lot and, especially, for large-scale data analysis, which needs a lot of compute for a few seconds. The biggest problem with the cloud is lack of control: a missing feature, a service that goes down, a bug you cannot diagnose without the server logs, a service that gets discontinued or expensive, lock-in, geopolitical risk, compliance.

Cloud native and the separation of storage and compute

Beyond the economic model, the cloud changed how data systems are built. The central idea of cloud native is building higher-level services on top of lower-level cloud services. The main example is object storage: a more limited API than a filesystem, but one that hides the machines, distributes the data and loses nothing when a disk fails.

The most important technical concept in the section, for me, is this one. In the traditional model, the disk is durable and the same machine stores and processes. In the cloud, a VM’s local disk is an ephemeral cache. The alternative, the detachable virtual disk, is not a disk: it is a service on other machines emulating a block device, and every I/O becomes a network call. That is why cloud native systems avoid virtual disks and manage small values in a separate service, keeping large blocks in object storage. The result is disaggregation: storage and compute become distinct responsibilities, and analyzing means moving data over the network.

flowchart LR
    subgraph T[traditional]
        N1[server] --- D1[(local disk)]
    end
    subgraph C[cloud native]
        C1[compute 1] <-->|network| OS[(object storage)]
        C2[compute N] <-->|network| OS
        C1 <--> META[(small values)]
    end

On operations: DevOps and SRE did not change the goal, they changed the process and the tools. In the cloud, capacity planning becomes financial planning and performance optimization becomes cost optimization. The customer still needs operations, but of a different kind: choosing, integrating and migrating services; application security and investigating degradations cannot be outsourced. In the authors’ words, the need for operations is as great as it has ever been.

Distributed versus a single node

The book lists nine reasons to distribute: inherent distribution, calls between cloud services, fault tolerance, scalability, latency, elasticity, specialized hardware, legal compliance and sustainability. And it lists the price.

Every network call can fail, and the worst case is a timeout with no answer: you do not know whether the other side received it, so retrying may not be safe. The network is slow compared to memory; with a lot of data, it is usually faster to bring the computation to the data. More nodes is not always faster, and the chapter cites a single-threaded program on one machine that beat a cluster of more than a hundred cores. Troubleshooting gets hard. And consistency between services becomes the application’s problem, because distributed transactions are rare in microservices.

flowchart TD
    Q1{users on different devices<br/>or data in another service?} -->|yes| D1[already distributed: handle network, timeouts, retries]
    Q1 -->|no| Q2{must survive the loss<br/>of one machine?}
    Q2 -->|yes| D2[distribute for fault tolerance]
    Q2 -->|no| Q3{data or load exceeds<br/>one large machine?}
    Q3 -->|yes| D3[distribute for scale]
    Q3 -->|no| S[stay on a single node]

My own flow, derived from the chapter’s reasons: distribute for a concrete reason, not by default.

The authors’ conclusion is direct: doing it on a single machine is usually much simpler and cheaper, and databases such as DuckDB and SQLite handle a lot of load on one node.

On microservices, the line most worth keeping: they are mostly a technical solution to a people problem. They let teams move without coordinating with each other, which is worth a lot in a large company and is overhead in a small one. The data rule is clear: each service with its own database. A shared database turns the entire schema into part of the API and lets one service’s query wreck the performance of the others. The cost: testing requires bringing up dependencies, each service needs its own infrastructure, and evolving an API breaks clients.

Serverless brings pay-per-use to code execution, at the cost of execution time limits, restricted runtimes and cold starts.

Law and society

The last section reminds us that data architecture is not decided by technical requirements alone. Since 2018 the GDPR gives people control over their data; Brazil’s LGPD does the same here. And automated systems decide who gets credit, who gets called for an interview, who is a suspect.

The concrete example is the right to be forgotten. Many data systems are built on immutable structures, such as append-only logs. How do you delete a record in the middle of a file that was never supposed to change? How do you delete what has already gone into derived data, such as a model’s training set? The law, on purpose, does not prescribe technology; it defines principles.

The cost of keeping data is not just the storage bill: it is a breach, a fine, and risk to the people themselves, when the data can reveal behavior that is criminalized somewhere. Hence data minimization, which runs against big data’s “keep everything”: collect for an explicit purpose, do not reuse it for another, do not keep it longer than necessary.

What I take from the chapter

  1. OLTP and OLAP differ in access pattern, audience and what the data represents. The separation is physical for four reasons, and the lake became the intermediate stop.
  2. Knowing what is the system of record and what is derived simplifies any architecture. The role depends on use, not on the tool.
  3. Cloud or self-hosting depends on the team’s competence and the predictability of the load. What really changed was the separation of storage and compute.
  4. Distributing is sometimes unavoidable, but do not rush to distribute what fits on one machine. Microservices solve a team problem and charge in complexity.
  5. Privacy regulation is an architecture requirement, not a legal appendix.

A parallel from real life

I worked on a high-volume transactional system, tens of millions of accounts and millions of entries a day. The primary database was the system of record. Around it, everything was derived: a balance cache, a read replica, the events published to other services, the copy that fed analytics. In theory, all of it could be rebuilt from the source. In practice, the question “what derives from what” was the one that settled the most architecture arguments, because it said who was right when two places disagreed.

The line in the chapter that hurt the most was the one saying derived data needs a process to propagate changes, and that databases do not help with it. That process, in my case, was the outbox pattern: the business row and the event written in the same transaction, a job draining the table to the broker. I wrote about the choice in CDC or outbox and about the trail it leaves in the APM in Distributed tracing through the outbox. Reading the chapter afterwards, I understood I had been solving exactly the problem it names: keeping derived data coherent with the system of record without a distributed transaction.

The second lesson I had already paid for before reading was the virtual disk. A yearly batch job over that database, managed in the cloud, did not hit CPU or the query plan. It hit the disk’s IOPS ceiling. Every read was a network call, exactly as the chapter describes, and the limit showed up in no database metric, only in the SKU documentation. Had I read the section on separating storage and compute earlier, I would have looked for that ceiling on day one, not on the first late night.

References



Share this post:

Comments

Comments live on GitHub Discussions; sign in with GitHub to join.


Previous Post
Creating new partitions without locking the root table