Chapter 12 is Kleppmann's capstone — his personal synthesis of everything in the book projected forward. Three big themes: (1) how to compose specialized data tools into a coherent whole, (2) how to get correctness guarantees without distributed transactions, and (3) the ethics of building data-intensive systems. The chapter also contains the only sustained first-person argument in the book, so opinions are explicitly marked.
Mission tie-in: This chapter is pure gold for system design interviews. The write path/read path framing, the unbundling metaphor, the end-to-end argument applied to databases, and coordination-avoiding data systems are all high-signal interview topics. The ethics section won't show up in a Google L5 loop, but it's essential for being a well-rounded engineer.
The central problem: no single tool serves all use cases. You need an OLTP database and a search index and a cache and a data warehouse and a stream processor. So you cobble them together. The hard part is keeping them consistent.
The naive approach: dual writes — application code writes to each system. Two fatal flaws (from Chapter 11): race conditions (different order at different systems) and partial failures (one succeeds, one fails). Both cause permanent inconsistency.
The better approach: designate a system of record and derive everything else from it. Derivation uses batch + stream processing to transform the source data into indexes, caches, materialized views, ML models, summaries — whatever downstream consumers need.
| Aspect | Distributed Transactions (2PC/XA) | Log-Based Derived Data |
|---|---|---|
| Ordering mechanism | Locks for mutual exclusion | Log for total order (state machine replication) |
| Integrity mechanism | Atomic commit (all-or-nothing) | Deterministic retry + idempotence |
| Timeliness | Linearizable (synchronous) | Asynchronous by design (eventual) |
| Fault propagation | Amplifies: one failure aborts all | Contains: fault in consumer doesn't affect others |
| Heterogeneous support | Poor (XA is fragile) | Good (log is a universal interface) |
| Performance at scale | Degrades (coordinator bottleneck) | Constant (sequential writes to log) |
Kleppmann's view: log-based derivation is the most promising integration approach. But he doesn't dismiss read-your-writes guarantees — they matter, and "eventual consistency is inevitable, suck it up" is not an acceptable answer without guidance.
A totally ordered event log (single leader) works for small systems but breaks down under:
Total order broadcast is equivalent to consensus (Chapter 9). Scaling consensus beyond single-node throughput in geo-distributed settings remains an open research problem.
Example: user unfriends ex-partner, then sends message complaining about them. If the causal dependency (unfriend → message) is lost, a notification service may incorrectly notify the ex. Solutions:
Batch and stream processing are converging. Spark breaks streams into microbatches on a batch engine; Flink runs batch as a special case of streaming. The distinction is blurring, but batch handles reprocessing historical data, while stream provides low-delay incremental updates.
Deterministic, pure functions with immutable inputs and append-only outputs — same philosophy from Chapter 10. Asynchrony is what makes log-based systems robust: a fault in one consumer is contained locally, unlike distributed transactions which abort all participants when one fails.
Schema migration analogy: railway gauge conversion. 19th-century England had competing railway gauges. The solution: add a third rail (dual gauge), run both old and new trains, then remove the old rail when conversion is complete. Gradual, reversible migration.
Same idea with data: maintain old schema and new schema side by side. Shift users gradually. If the new view has bugs, switch back. Every stage is reversible, reducing risk.
Nathan Marz's proposal: record immutable events; run batch + stream in parallel. Stream produces approximate updates quickly; batch produces corrected versions later.
Problems Kleppmann identifies:
Unified systems (Flink, Beam, Cloud Dataflow) supersede the Lambda approach: replay historical events through the same engine, exactly-once semantics, event-time windowing. One engine, not two.
A database internally does: storage + indexing + replication + query optimization + materialized views + triggers + constraints. These are tightly integrated features.
Unbundling = taking those features and implementing them as separate, loosely coupled components connected by streams. The search index, cache, ML model, and analytics warehouse are like different index types on a single logical database.
A unified query interface over heterogeneous storage engines — PostgreSQL Foreign Data Wrappers, polystores (BigDAWG). Applications access specialized engines directly or combine data through the federated interface. Solves read-side integration only.
The harder problem: keeping writes in sync across systems. The unbundled approach uses CDC + event logs to propagate writes — like Unix pipes for databases. Loose coupling manifests as:
The unbundled database needs its Unix shell — a declarative language for composing storage and
processing systems. Kleppmann dreams of:
mysql | elasticsearch — a pipe that captures database changes and indexes them
automatically, like a declarative CREATE INDEX across heterogeneous systems.
Early research: differential dataflow (Naiad) — incrementally update materialized views as inputs change. Think spreadsheet recalculation for distributed data systems.
Derived datasets need transformation functions. Some are generic (secondary indexes —
CREATE INDEX). Others are application-specific (ML feature engineering, cache
invalidation logic). The latter require custom code — and this is where databases fall short
(triggers, stored procedures are afterthoughts). Stream processors are the better execution
environment.
Modern web apps deploy stateless services + state in databases. The database is a mutable shared variable — but you can't subscribe to changes, only poll. The dataflow approach flips this: subscribe to state changes ahead of time, store locally, query locally (no network request).
| Aspect | Microservices (REST) | Dataflow (Stream) |
|---|---|---|
| Communication | Synchronous request/response | One-directional async streams |
| Dependency | Need other services online (cascading failures) | Pre-subscribed local state; offline-tolerant |
| Latency | Network round-trip per request | Local query (same machine/process) |
| Fault tolerance | Retry/timeout/circuit-breaker | Log replay + idempotent processing |
| Example | Query exchange rate service on purchase | Subscribe to exchange rate stream, store locally, query on purchase |
The dataflow approach is faster and more robust: the fastest network request is no network request. Instead of RPC, perform a stream join between purchase events and exchange rate update events.
Every derived dataset has two paths:
The derived dataset is where write and read paths meet. Indexes, caches, and materialized views shift the boundary: more work on write path = less work on read path, and vice versa.
Twitter example from Chapter 1 reprise: celebrities get fan-out-on-write (more write work, fast reads); ordinary users get fan-out-on-read (less write work, slower reads). The boundary is drawn differently per user.
Extend the write path to end-user devices. Subscribe to state changes (via WebSocket, SSE), maintain a local cache, continue working offline, sync when reconnected. Consumer offset technique (Chapter 11) works for individual devices — each device is a small subscriber to a small stream.
Treat read requests as event streams. Route read + write events through the same stream processor. A read becomes a stream-table join between the query and the database partition. Logging reads adds causal tracking (what state did the user see before their action?) at the cost of additional storage and I/O.
Complex queries that span partitions (e.g., union of follower sets for a URL, fraud detection joining reputation databases) can be expressed as streams of read events routed through a stream processor's partitioning and joining infrastructure. Storm's Distributed RPC is an example.
Stateful systems remember things forever — bugs corrupt data permanently. ACID transactions have been the standard tool for correctness for 40 years, but they're weaker than they seem (weak isolation confusion, Jepsen findings) and expensive at scale.
Classic example from Saltzer, Reed, Clark (1984): TCP suppresses duplicate packets, but that doesn't prevent duplicate HTTP POSTs if the response is lost. The application itself must handle end-to-end deduplication.
Same principle applies to databases: serializable transactions don't save you from application bugs. If the app writes incorrect data, no transaction isolation level helps. End-to-end integrity requires application-level mechanisms.
Making an operation naturally idempotent is the cleanest approach:
Example:
INSERT INTO requests (request_id, ...) VALUES ('UUID', ...)
with UNIQUE (request_id). If a retry inserts the same UUID, the unique constraint
aborts the second transaction, preventing double charging.
Enforcing uniqueness (usernames, seat bookings) requires consensus: concurrent requests with the same value need a single decision on which wins. Standard approach: single leader node. Can scale by partitioning on the unique value (hash of username).
In log-based systems: partition the log by the unique value (hash of username). Stream processor consumes one partition on a single thread, unambiguously orders requests, and decides first-wins. Same as implementing linearizable storage via total order broadcast (Chapter 9).
Bank transfer from A to B spans 2+ partitions. Traditional approach: distributed transaction across partitions (expensive). Log-based approach: client generates a request ID, appends to request log (single partition). Stream processor reads the request, emits debit instruction to A's partition and credit instruction to B's partition. Downstream processors deduplicate by request ID.
Key insight: atomicity is achieved by single-object write to the request log — either the request appears or it doesn't. The multi-partition effect is derived deterministically from that single durable write. No distributed transaction needed.
| Property | Definition | Violation consequence | Example |
|---|---|---|---|
| Timeliness | Users observe up-to-date state | Temporary inconsistency (eventually resolved) | Stale credit card statement (24h delay) |
| Integrity | No data loss or corruption | Permanent inconsistency (requires repair) | Statement balance doesn't equal sum of transactions |
Kleppmann's strong claim: integrity is more important than timeliness. Violations of timeliness are annoying; violations of integrity are catastrophic. Dataflow systems decouple them: async processing provides no timeliness guarantees, but exactly-once semantics, idempotence, and deterministic derivation preserve integrity.
Many real-world constraints don't need strict linearizability:
The cost of apology is a business decision. If it's acceptable, linearizable constraints aren't needed — go ahead optimistically and fix up later.
Two observations combine:
Result: coordination-avoiding data systems that operate across datacenters with multi-leader async replication. No synchronous cross-region coordination. Weak timeliness, strong integrity. Can still introduce synchronous coordination where needed (e.g., before an operation with irreversible consequences) — not everything needs to pay the cost.
All our system models make assumptions (fsync works, memory doesn't corrupt, CPU arithmetic is correct). These are probabilities, not absolutes. Random bit-flips are rare but happen (rowhammer attacks, cosmic rays). Software bugs in databases themselves exist.
Kleppmann argues for self-auditing systems: continually read back data, compare replicas, run redundant derivations in parallel. HDFS and Amazon S3 already do this — background processes that check file integrity and move data off failing disks. Most systems don't.
Event sourcing makes auditability natural: the event log is the truth; derived state is deterministic and repeatable. Run the same log through the same code = same state. Merkle trees (used in certificate transparency, cryptocurrencies) can prove integrity cryptographically. Kleppmann sees this as an area to watch.
End-to-end integrity checks include the entire pipeline — disks, networks, services, algorithms — in one check. Continuous verification gives confidence to evolve faster (like automated testing).
The final section — ethics of data-intensive applications. Kleppmann argues engineers have a responsibility to consider the real-world consequences of their systems.
Algorithms that predict recidivism, loan defaults, insurance risk directly affect people's lives. Machine learning can amplify existing biases: if input data is discriminatory, the output will be too. Postal code predicts race in segregated neighborhoods; an algorithm using postal code encodes racial bias.
Accountability gap: if a human judge makes a mistake, there's appeal. If an algorithm makes a mistake, who is accountable? Credit scores at least use relevant facts (borrowing history); ML scores use opaque correlations with no recourse for errors.
Predictive systems can create self-reinforcing downward spirals. Example: credit score affects employability; joblessness worsens credit score; further reduces chances of employment. Systems thinking is required to anticipate these effects.
Kleppmann's strongest language in the book: "we have built the greatest mass surveillance infrastructure the world has ever seen." He proposes renaming "data" to "surveillance" in common phrases to expose the reality: "surveillance-driven organization," "surveillance warehouse," "surveillance scientists."
The Industrial Revolution brought enormous benefits (economic growth, living standards) but also terrible harms (child labor, pollution, worker exploitation) that required regulation to fix. "Data is the pollution problem of the information age" (Bruce Schneier).
Kleppmann's call to action: self-regulate data collection, purge data when no longer needed, build cryptographic access controls, educate users. "Ubiquitous surveillance is not inevitable — we are still able to stop it."
The key ideas of Chapter 12:
Select the correct answer for each question.
Scoring: 8/10 = Mastery. 6/10 = Review. <6/10 = Reread sections.
1. What is the fundamental advantage of log-based derived data over distributed transactions (2PC) for integrating heterogeneous data systems?
Log-based systems provide synchronous linearizability Better fault containment and heterogeneous support without XA They eliminate the need for durable storage2. What is the limits of total ordering in geo-distributed systems?
Total order is impossible in any distributed system Multi-DC network latency forces separate leaders per DC, breaking total order Partitioning the log solves both throughput and geo-distribution3. What does the lambda architecture combine, and what is Kleppmann's main criticism of it?
It uses batch OR stream processing; the problem is throughput Parallel batch + stream on same events; unnecessary operational complexity of dual codebases Stream-only processing with batch for backup; the problem is merging outputs4. What does "unbundling a database" mean in Kleppmann's framework?
Merging OLTP, search, analytics into a single database product Decomposing database features into independent stream-connected components Removing stored procedures from the database5. What do the write path and read path represent in a dataflow system?
Write path = database writes; read path = application reads Write path = eager precomputation; read path = lazy serving; they meet at the derived dataset Write path = batch processing; read path = stream processing6. How does dataflow architecture replace synchronous microservice RPC calls?
It uses webhooks instead of REST calls Subscribe to state change streams ahead of time, store locally, query locally Cache all possible query results in a local database7. What does the end-to-end argument mean for databases?
Serializable transactions guarantee application-level correctness Low-level features (transactions, TCP) are insufficient — applications need end-to-end mechanisms All correctness guarantees should be pushed to the storage layer for performance8. How can a money transfer between two accounts be implemented without a distributed transaction across partitions?
Append credit and debit independently to each partition Write request with unique ID to a single partition; derive multi-partition effects deterministically A unique index per partition can't detect cross-partition duplicates9. What is the distinction between timeliness and integrity in dataflow systems?
Timeliness and integrity are the same property — both require synchronous coordination Integrity is permanent correctness; timeliness is temporary staleness — dataflow guarantees integrity without timeliness Dataflow requires strong timeliness and provides weak integrity10. What does Kleppmann argue about the responsibility of data system engineers?
Engineers should focus exclusively on technical excellence and let product teams handle ethics Engineers carry responsibility for how their systems affect the world — surveillance is not inevitable Expose what tracking means and build mechanisms for user agencyNo single tool fits all → compose specialized systems
Problem: dual writes → race conditions + partial failures
Solution: system of record + derived datasets via batch/stream
Log: async, fault-contained, heterogeneous, deterministic retry + idempotence
2PC: synchronous, fault-amplifying, XA-hell, locks for ordering
Total order broadcast ≡ consensus → limits: throughput, geo-DC, microservices, offline
Batch (Hadoop, exact) + Stream (Storm, approximate) in parallel
Problems: dual codebase, output merge, incremental batches ≠ batch
Unified engines (Flink, Beam) → replay + event-time + exactly-once
Database features → separate stream-connected components
Federated DB → unifying reads (Postgres FDW, polystores)
Unbundled DB → unifying writes via CDC + event logs
Missing: declarative pipe language (mysql | elasticsearch)
Write path → eager precomputation (on data arrival)
Read path → lazy serving (on user query)
Indexes, caches, MVs shift boundary between them
Twitter: celebrity fan-out-on-write ; pleb fan-out-on-read
TCP dup-sup ≠ app-level dedup ; serializable tx ≠ bug-free app
Solution: operation ID (UUID) → unique constraint → dedup
Uniqueness → consensus → partitioned log with single-thread consumer
Client → request log (single-partition write, atomic)
Stream → deterministic derive → debit to A's partition + credit to B's partition
Downstream → dedup by request ID → exactly-once
Key: single-object write gives atomicity; derivation gives multi-partition effect
Timeliness → up-to-date state (temporary violation)
Integrity → no corruption (permanent violation, needs repair)
Dataflow guarantees integrity without timeliness
Coordination-avoiding: loose constraints + apology ≪ strict linearizability
Assume hardware + software will eventually corrupt data
Self-auditing: read back, compare replicas, redundant derivation
Event sourcing → reproducible state from deterministic replay
Merkle trees → cryptographic integrity proof
Predictive analytics → bias amplification, no accountability
Feedback loops → credit score ↓ → jobs ↓ → credit score ↓ (spiral)
Surveillance ≠ data — rename to expose reality
Privacy = right to choose what to reveal, not secrecy
Data = toxic asset (breaches, bankruptcy, government coercion)
"Data is the pollution problem of the information age" — Schneier
Call to action: self-regulate, purge, educate, build cryptographic controls
Primary source: Kleppmann, M. (2017).
Designing Data-Intensive Applications, Chapter 12: "The Future of Data Systems."
O'Reilly Media.
Recommended supplements: Jay Kreps' "The Log: What Every Software Engineer
Should Know" (for the unbundling thesis), the Dataflow Model paper (Akidau et al., VLDB 2015),
the end-to-end argument paper (Saltzer, Reed, Clark, ACM TOCS 1984), Pat Helland's "Life
Beyond Distributed Transactions" (CIDR 2007), "Immutability Changes Everything" (CIDR 2015),
and for ethics: Cathy O'Neil's "Weapons of Math Destruction," Bruce Schneier's "Data and
Goliath," and the ACM Code of Ethics.
Ask follow-up questions to drill into: how differential dataflow works, the exact mechanics of coordination-avoiding data systems, the CALM theorem, how to design end-to-end exactly-once with operation IDs, the Lambda Architecture debate in detail, practical DB unbundling with Kafka/Debezium/Streams, engineering ethics in practice, or how to build auditing systems with Merkle trees.