
You update an address, receive success and refresh the page. The old address returns. The write reached the leader, but a lagging replica served the read. There is insufficient information to conclude that data was lost. There is a mismatch between the interface's apparent promise and the reading path's behavior.
Martin Kleppmann's Designing Data-Intensive Applications helps articulate that difference. I recommend it for discussing data systems beyond product names. The expected benefit is comparing mechanisms against workloads, failures and observable guarantees before choosing technology or announcing that a system is consistent.
This review concerns Martin Kleppmann's first edition, published in March 2017 according to O'Reilly's listing. It neither reviews the second edition nor claims to cover the latest edition. The structure moves through foundations, distributed data and derived data.
That breadth connects local decisions with later consequences. An index affects writes and reads; replication changes visibility; a derived report adds another update path. Follow an application operation through the mechanisms rather than memorizing every mentioned product.
For our profile example, describe operations before selecting storage: retrieve a person by identifier, update their address and report recently modified profiles. These access data differently and need different policies for delay and authorization.
An identifier index does not automatically support an update-time range query. Another index can reduce record-location work while adding write maintenance and storage. Evaluate access frequency, volume and distribution instead of concluding that more indexes always help.
Apply the same reasoning to storage structures. Trees and log-based structures organize reading, writing and reorganization differently. Labels such as write-optimized cannot replace experiments with relevant queries, record sizes and operating conditions. The book supplies investigative lenses rather than a universal ranking.
The first edition's replication chapter covers reading your own writes among lag-related problems. Our original exercise uses that problem to separate an interface promise from a topology choice.
Assume one leader L, asynchronous replica R and session S. Profile U-7 initially contains revision 8, address Street A. In this teaching scenario, the leader acknowledges after persisting revision 9, Street B, locally, without waiting for R. No node fails during the initial sequence.
| Logical order | Operation | State and observation |
|---|---|---|
T0 | Initial state | L=v8/Street A, R=v8/Street A |
T1 | S updates through L and receives success | L=v9/Street B, R=v8/Street A |
T2 | After success, S refreshes through R | Returns v8/Street A |
T3 | R applies the update | L=v9/Street B, R=v9/Street B |
These markers indicate order, not measured times. The exercise demonstrates no actual deployment and places no deadline on T3. At T2, the leader has persisted data that the replica has not applied. That differs from rejecting or losing the write.
Define the contract: after acknowledging revision 9, session S cannot see an earlier revision of U-7. Routing those reads to L is one option while the leader remains stable in our scenario. Other queries may still use replicas when their contracts permit lag.
Another option carries a write marker and reads only from a node that has applied that position or later. This requires infrastructure support and verifiable semantics. Revision 9 represents one profile's monotonic order under one leader here; it is not a generally safe token for comparing leadership epochs or different partitions.
Pinning a session to just any replica is insufficient: it may remain behind the write. Waiting a fixed interval cannot establish application of the update either, especially as lag grows. Specify whether the application waits, chooses another source or reports explicit unavailability.
This policy cannot demonstrate system-wide linearizability. It protects a particular relationship between S's write and reads without establishing a global order across every client. It also does not independently determine another device's view when that device lacks the session marker.
Routing reads to the leader also shifts load and may add network distance. Waiting for replica application retains a different reading path, but needs a deadline and a policy for exhausting it. An application cannot promise a recent, fast response under every possible delay. Product decisions must identify the consequence users can tolerate, including explicit unavailability when the guarantee cannot be met. That is a concrete trade-off to compare rather than a reason to hide every outcome behind a generic retry.
Free and paid products to turn ideas into a foundation you can actually ship.
13 products availableContinue exploring similar topics

Apply Michael Nygard's approach to timeouts, isolation and recovery through a controlled failure exercise.

Turn passive study into a learning project with direct practice, feedback and spaced retrieval.

Critical review of Rob Fitzpatrick: investigate real episodes, separate facts from hypotheses and do not mistake interviews for market validation.
A 47-point checklist to find bugs, security risks, and performance issues before launch.
Production-tested templates trusted by developers. Save weeks of setup on your next project.
Change the assumption: L fails after acknowledging v9 but before R receives it. If policy promotes R without recovering that write, the acknowledged address may disappear from available state. Local persistence cannot equal survival of losing that node.
A survival promise must specify required acknowledgments and eligible leadership candidates. Involving more participants can add waiting and communication dependencies. A lagging replica creates a visibility problem; promotion without the acknowledged write adds loss risk. Their remedies and costs differ.
Losing T1's response introduces another situation: an unknown outcome. A timeout cannot reveal whether acknowledgment occurred. A stable operation identity and result lookup can help if the system maintains that contract. A new identity per attempt makes repetition of the same intention harder to recognize.
Suppose editing an address must also append an audit record. If both belong to one commit, write them atomically in storage providing that guarantee. Test failure between the steps and absence of partially applied state.
That cannot automatically make a later replica read current. Write atomicity and read visibility concern different parts of the operation. Nor should an isolation-level name become a promise about every concurrent sequence. Examine documented behavior and the anomalies your application must prevent.
Kleppmann challenges simplified labels in Please stop calling databases CP or AP, emphasizing precise definitions and operation-specific properties. For profiles, asking who reads what after which acknowledgment clarifies more than assigning two letters to the whole database.
A profile may feed a search index and report. Updating the primary record need not update every representation simultaneously. If search returns Street A while the editing screen displays Street B, establish whether this is permitted delay or a product-contract violation.
Batch processing can rebuild a report from a bounded input set; stream processing follows arriving changes. Both require decisions about repetition, completeness and format evolution. Understand how results are produced and rebuilt without claiming one mechanism always replaces the other.
Identify the reference source, how far derivation has progressed and failure policy. Reprocessing may recover results while repeating external effects if a consumer cannot distinguish reconstruction from a new action. The path between persisted data and user experience deserves the same care as the initial write.
Choose a controlled environment and actual replication configuration. Record version, topology and acknowledgment policy. Reproduce replica-application lag, execute the write and compare session reads. Separate read-your-writes expectations from any node-failure survival promise.
Then simulate a lost response and leadership change through available mechanisms. Observe whether the application recovers the outcome or communicates uncertainty. Never use an experiment to assert guarantees its environment was not configured to provide.
This review's table executes none of those tests. It helps write expectations and locate missing evidence. Success in one scenario cannot cover every failure; retain explicit cases, assumptions and verification limits.
The comparative method is its strength. You gain questions distinguishing write costs, latency, availability and recovery. These challenge both popularity-based product choices and the belief that one mechanism solves every risk.
Breadth is also a limitation. Understanding concepts differs from operating a migration or recovering a database under pressure. Readers may acquire vocabulary before operational judgment. Combine reading with documentation, experienced operators and experiments exposing configuration consequences.
Products and examples in the 2017 edition reflect their period. Use concepts as a starting point and check deployed-version documentation before transferring a product-specific assertion. Historical comparisons cannot establish a managed service's current behavior or automatically include its contractual limits.
I recommend it to developers familiar with basic SQL, indexes and process communication. Running a large cluster is unnecessary, but observing a write, read and network failure helps. Beginners can start with those experiments and return to chapters as questions emerge.
If the immediate need is learning table queries or fixing a connection, introductory material and database documentation may pay off sooner. Avoid introducing distribution merely to practice the book. Simple storage meeting the contract avoids coordination your product may not need.
Implementing DDD helps formulate business invariants. Release It! complements operational failure analysis. Clean Architecture discusses dependencies. DDIA investigates mechanisms that must sustain those decisions; separating layers cannot select a read guarantee.
Describe a real operation: inputs, query, write and visible consequence. Read storage and replication to question data paths. Study transactions and failures next, revising the timeline. Explore derived processing when search, reporting or another separately maintained result exists.
For each alternative, state a desired guarantee, an accepted cost and an assumption requiring tests. Compare this with current implementation before proposing replacements. This reading practice carries no promise that it ends with a database migration.
I recommend the first edition for the reasoning it develops. The desired result is distinguishing data not yet visible, lost data and an unknown outcome, explaining how each reaches users. Sound decisions begin with clear promises and willingness to verify what sustains them.