The Operator's
Field Manual
DDIA, distilled for the interview room and the job
How to use this manual
This is not a summary you read once. It's a reference you drill. The body of each chapter is the concept in plain language; the colored boxes are where the interview leverage lives. Read a chapter, then cover the page and answer its Interview box out loud. If you can also teach the Staff lens box to someone else, you're interview-ready on that topic.
How this interactive guide works
- Left sidebar jumps to any chapter; the active one highlights as you scroll, and finished sections get a check.
- Top bar shows total reading time, sections done, your live pace, and a study timer you can play or pause.
- Each section header shows its own read time; tick Mark done, or just scroll past and it auto-completes.
- Turn on Study mode (bottom left) to blur the key takeaways and the "Say this" lines so you can self-test, then click any blurred line to reveal it.
- Your progress and timer are saved in your browser, so you can close the tab and pick up where you left off.
The data problems that decide AI-lab and quant hiring - idempotency, exactly-once delivery, schema evolution, lineage, consistency under partition, governance of sensitive data - are a small set of recurring ideas. DDIA is the vocabulary that lets you name them the way interviewers expect - so you can turn real engineering experience, from any domain, into a hire decision.
New to some of these terms? Every chapter opens with an ELI5 - "in plain words" - box (the violet one) that translates the jargon into plain language with everyday analogies. If a word is unfamiliar, read that box first, then the section, then the rest of the boxes. You do not need a distributed-systems background to start.
And to take you the other way - up to staff level - most chapters add a Staff lens box (graphite) with the tradeoffs, failure modes, and capacity / cost judgment a staff engineer is expected to bring, plus a Use cases box (green) showing where the concept actually gets used across payments, ML, and markets. Read each chapter as a climb: plain words → mechanism → modern stack → staff-level judgment → where to apply it.
The box legend
The chapter's jargon translated into everyday language with simple analogies. New to a term? Read this box first, then the section.
The judgment a staff engineer is expected to bring: tradeoffs, failure modes, capacity / cost reasoning, and when the textbook answer is wrong.
Concrete real-world scenarios - across payments, AI / ML, quant, and general systems - showing where each concept actually gets used.
The question you'll actually be asked on this topic, plus the follow-ups that separate a senior answer from a junior one. Rehearse aloud.
What replaced or refined this idea since the book was written: the lakehouse, Flink, feature stores, vector DBs, CDC. Interviewers reward knowing the book and the present.
A behavioral-interview narrative skeleton (Situation / Task / Action / Result) you can fill with your own experience.
The thing that sounds right but flags you as out of date or shallow. Avoid these on purpose.
The one sentence to keep if you forget everything else on the page.
The interview battlefield
Before the concepts, the context. The three families of role targeted here test overlapping but distinct things. Knowing the shape of each loop tells you which chapters to over-prepare.
What each loop is really testing
| Target | Loop shape | What they actually probe | Over-index on |
|---|---|---|---|
|
Anthropic Data Engineer |
Recruiter → CodeSignal (build a small system from scratch) → HM screen → 2-3 deep dives (DSA/LLD, system design, practical data) → project deep dive → values & safety → team match | Production-quality code (not LeetCode tricks); pipelines for training data, RLHF preference annotations, eval-dataset versioning; reliability for model-training workflows; mission alignment | Ch 4, 9, 11, 12 + Part V whole |
|
OpenAI Applied / Data Eng |
Recruiter → technical phone (live Py/SQL) → take-home or pair → onsite: system design, "practical data eng" (debug Spark / optimize a pipeline), coding, stakeholder | Define a metric precisely before building; ingest millions of events/sec queryable in minutes; backfills running beside live pipelines; data-drift detection. Judgment with AI tools is explicitly evaluated | Ch 1, 6, 10, 11 + DQ & backfill |
|
Quant Citadel · Two Sigma · Jane St · HRT |
OA (CodeSignal/HackerRank, medium-hard) → 1-2 CoderPad screens (often C++/OS internals) → back-to-back onsite rounds → team fit | Correctness culture ("almost works" = reject); low-latency market-data ingestion normalizing N exchange feeds; time-series storage; verbalize invariants & edge cases as you code | Ch 3, 6, 8, 11 + time-series |
Whatever your background, these interviews reward one story above all others: correctness at scale. Lead with a concrete case where a data bug would have had a real cost - money, safety, a broken model, a bad trade - and you enforced idempotency, exactly-once, lineage, or strong consistency to prevent it. Financial-grade data discipline (idempotency, reconciliation, auditability) maps directly onto AI training and eval pipelines and onto quant market-data systems, so if you have it from any domain, name the transfer explicitly. Then back the claim with the story framework in Part VII.
The system-design prompt bank (drill these to a 35-45 min running time)
Across all six firms, design prompts cluster into ten archetypes. Each maps to chapters in this manual - the cross-reference tells you what to revise before a given onsite.
| # | Prompt archetype | Seen at | Manual sections |
|---|---|---|---|
| 1 | Compute MAU/DAU on a billion-event/day stream - define "active" first | OpenAI | Ch 6, 10, 11 |
| 2 | Design a feature store (offline + online, no training/serving skew) | Anthropic · OpenAI · quant | Ch 11, 12 · VI |
| 3 | Real-time exactly-once event processor (ad / payment events) | all | Ch 11 · V-3 |
| 4 | Metrics / eventing system with rate limits, hot keys, backpressure | OpenAI · quant | Ch 6, 11 |
| 5 | Ingest + index 1B documents with concurrent crawling | Anthropic | Ch 3, 6, 10 |
| 6 | Market-data pipeline normalizing feeds from N exchanges | quant | Ch 4, 6, 11 |
| 7 | Data-quality / drift-detection framework with alerting | OpenAI | VI · Ch 1 |
| 8 | Backfill 3 years of data while the live pipeline runs | OpenAI | Ch 10, 11 · V-3 |
| 9 | Debug a p95 latency regression 100 ms → 2000 ms | Anthropic | Ch 1, 8 |
| 10 | RLHF / eval-dataset versioning & lineage system | Anthropic | Ch 4, 12 · VI |
Don't start drawing boxes. Start with requirements + invariants: (1) restate functional goals, (2) pin numbers - QPS, event rate, payload size, latency SLO, retention; (3) name the correctness invariant ("no event double-counted," "features identical in training and serving"); (4) state read/write ratio and access pattern. Then design. Interviewers at all three target types score "defines the problem before solving it" heavily - OpenAI's MAU prompt is explicitly a trap for people who code before defining "active."
- Reaching for tools before tradeoffs. Saying "I'd use Kafka and Spark" before stating why loses the round. Name the property you need, then the tool that provides it.
- Claiming exactly-once without mechanics. If you say "exactly-once," you must be able to explain idempotent producer + transactions + checkpoint + idempotent sink (Part V-3). Otherwise it reads as buzzword.
- Faking a language. At HRT/Jane Street, don't pretend C++/OCaml depth you don't have. Pick the Python-heavy group (HRT R&D ≈ 70% Python) and be honest.
Reliable, scalable, maintainable
Three words do all the work when we judge a data system. Define them precisely and you have the rubric every architecture decision is graded against - including in the room.
- Fault vs failure - a fault is one part hiccuping; a failure is the whole system going down. Good systems stop faults from turning into failures.
- Fault-tolerant - keeps working even when a part breaks - like a car that still drives on a spare tire.
- Latency vs throughput - latency = how long one request takes; throughput = how many you handle per second.
- Percentile / p95 / p99 - line up all response times slowest-to-fastest. p99 is how slow it feels for the unluckiest 1 in 100. The average hides that unlucky person; percentiles don't.
- Tail-latency amplification - if one click waits on 100 servers, the single slowest server decides how slow the click feels.
- Scale up vs scale out - buy one bigger machine vs add more machines.
Reliability - keep working when things go wrong
A fault is one component deviating from spec; a failure is the system as a whole stopping its service. The goal of a reliable system is to prevent faults from becoming failures - i.e., to be fault-tolerant. Three fault families:
- Hardware faults - disks die, RAM corrupts, a rack loses power. Historically handled with redundancy (RAID, dual power, failover). At scale, you assume machines fail constantly and tolerate it in software rather than chasing five-nines hardware.
- Software faults - a bug triggered by a specific input, a runaway process eating a shared resource, cascading failures. Harder than hardware faults because they're correlated: the same bug hits every node at once.
- Human faults - misconfiguration is the leading cause of real-world outages. Mitigate with good abstractions, sandboxes, fast rollback, thorough monitoring, and blameless culture.
The mature posture is to induce faults deliberately (chaos engineering) so you've already seen the failure modes before production does.
Scalability - keep performance as load grows
Scalability is not a yes/no property; it's the question "if load grows in this way, what are our options?" To ask it you must (1) describe load with load parameters (requests/sec, read:write ratio, simultaneous users, fan-out) and (2) describe performance with the right metric.
The right metric is almost never the average. Use the response-time distribution and report percentiles: median (p50) for the typical experience, and p95 / p99 / p999 tail latencies for the unlucky requests - which are often your most valuable customers (the ones with the most data). Two ideas you must be able to say cleanly:
- Tail latency amplification: if a single user request fans out to 100 backend calls and waits for all of them, the slowest of those 100 defines the user's latency. So a backend's p99 becomes the user's typical experience. This is why p999 matters even though it's "only 0.1%."
- Head-of-line blocking: a few slow requests hold up the queue behind them, inflating measured latency for fast requests too. Measure latency from the client's view, not the server's.
Scaling is up (a bigger machine - simple, has a ceiling) vs out (more machines - elastic, complex). Modern systems blend both and aim for elasticity (auto-add capacity under load).
Maintainability - keep it workable for the humans
Most of a system's cost is in its life after launch. Three sub-properties: operability (make routine ops easy - good telemetry, predictable behavior), simplicity (manage complexity with clean abstractions so new engineers can reason about it), and evolvability (make change easy - the agility to add requirements without a rewrite).
These three properties were renamed and operationalized by the SRE movement: reliability is now expressed as SLOs with an error budget (you're allowed to be down X; spend it on velocity). "Operability" became observability - metrics, logs, and traces unified under OpenTelemetry, with the mantra "you can't fix what you can't see, and p99 is the SLO." "Simplicity / evolvability" drove the platform-engineering wave (internal developer platforms, golden paths).
"Why report p99 instead of average latency?" Because averages hide the tail; one 10-second request among a thousand 10-ms requests barely moves the mean but is exactly the experience you must fix. Follow-up: "What's tail latency amplification?" When a request fans out to many services and waits for all, the slowest dominates, so backend p99 ≈ user p50. Follow-up: "How would you investigate p95 jumping 100 ms → 2000 ms?" (Anthropic's real prompt) - bound the problem: is it all requests or a subset? Check a recent deploy, then walk the fan-out with traces, look for a hot partition, GC pause, lock contention, a slow downstream, or a cache that started missing. Confirm with a metric before you change anything.
- Reliability is a budget, not an absolute - set SLOs from the cost of being wrong and spend the error budget deliberately on velocity. The staff job is defending that budget against both "five-nines everything" and "ship and pray."
- Tail latency is a queueing problem - the lever is concurrency limits, hedged requests, and load shedding - not a faster single call. Reason with Little's Law (L = λW) about queue depth and when a system tips into congestion collapse.
- Scaling out has a ceiling - past a point, more nodes add coordination/coherency cost (Universal Scalability Law - throughput can actually fall). Most real wins remove coordination and per-request fan-out, not add machines.
- Optimize for change - maintainability dominates lifetime cost; favour reversibility, blast-radius isolation, and clear contracts over cleverness.
- Payments - settlement pipelines with a hard finish-by SLO and an error budget that freezes risky deploys near month-end close.
- AI / ML - training-data freshness drives GPU-hour waste - a late feature table idles an expensive cluster, so p99 freshness is the SLO.
- Quant / markets - feed handlers where tail latency (not average) decides whether you miss a tick; hedged reads and load shedding through the open/close burst.
- General - any fan-out service (search, microservice mesh) adopting concurrency limits + backpressure to hold p99 under spikes.
Reliability = faults don't become failures. Scalability = a plan for load growth, measured in percentiles not averages. Maintainability = operability + simplicity + evolvability. Everything else in the book is in service of these three.
Data models & query languages
The data model is the most far-reaching decision you make, because it shapes how you think about the problem. Pick it from the access pattern, not from fashion.
- Relational database - data in neat, linked tables - think spreadsheets that reference each other by ID.
- Document database - each record is a self-contained blob (a whole order, with its line items, stored together in one place).
- Graph database - data drawn as dots and the lines between them - great when the connections themselves matter (who paid whom).
- Vector database - finds things by meaning or similarity instead of exact text - "show me shirts like this one."
- Schema - the agreed shape of your data: which fields exist and what type each is.
- Join - stitching two tables together using a shared key.
- Normalize vs denormalize - store each fact once (tidy, no duplicates) vs copy it around in advance (faster to read).
Relational vs document
The relational model stores data in tables of rows and lets you join freely at query time; the schema is enforced on write (schema-on-write). The document model (JSON-ish trees, as in MongoDB or DynamoDB) stores related data together and interprets structure on read (schema-on-read). The trade is structural:
- One-to-many / tree-shaped data favors documents - locality means one read pulls the whole object (a résumé, an order with its line items). No joins needed.
- Many-to-many relationships favor relational - documents force you to either denormalize (and fight update anomalies) or emulate joins in application code, which is slow and error-prone.
- Schema-on-read wins when records are heterogeneous or the shape changes often; schema-on-write wins when you want guarantees and the structure is stable.
The impedance mismatch - the friction between in-memory objects and relational tables - is what document stores were built to remove. The two models have since converged: relational engines store and index JSON; document stores added (limited) joins.
Graph models - when relationships are the data
When many-to-many relationships dominate and you traverse them (friends-of-friends, money flowing entity-to-entity, supply chains), a graph model fits: property graphs (vertices + edges with properties; queried in Cypher/Gremlin) or triple stores (subject-predicate-object; queried in SPARQL/Datalog). The point of a graph database is that a multi-hop traversal that would be a chain of expensive joins in SQL is a cheap, natural operation.
Vector databases are a first-class data model now and were not in the book. They store high-dimensional embeddings and answer "nearest-neighbor" queries for semantic search and RAG. The dominant index is HNSW (logarithmic-ish query time on a navigable small-world graph). Options span pgvector (inside Postgres - simplest if you already run Postgres), Pinecone (managed/serverless), Milvus and Qdrant (high-performance, strong filtered search), and Weaviate (multimodal). Hybrid search - combining vector similarity with keyword/BM25 - is now table stakes. If an AI-lab interviewer asks about RAG storage, this is the vocabulary.
"SQL vs NoSQL - how do you choose?" By access pattern and consistency need, not by scale myth. Known, join-heavy, evolving queries → relational. Known-up-front key-based access at huge volume → a document/wide-column store designed for it. Follow-up: "Model a payments fraud-detection system." Entities (cards, devices, merchants, accounts) are vertices; transactions are edges; community detection over the graph surfaces fraud rings. Follow-up: "Normalization vs denormalization?" Normalize for OLTP write-integrity (one fact, one place); denormalize for OLAP read speed (star schema, precomputed joins).
- The model is a one-way door - re-keying a 10-TB wide-column table or moving document→relational is a multi-quarter program. Model for the access pattern you'll have in three years, not today's.
- Polyglot persistence is the real state - one domain spans relational, document, graph, columnar, and vector. The staff skill is drawing clean boundaries with one source of truth - not picking a single database.
- Documents externalize integrity - they push joins and constraints into application code, and that debt compounds. Wide-column forces a table-per-query; denormalize on purpose and own the write amplification.
- Don't over-reach for graph engines - under a few million edges, a recursive CTE or adjacency table in Postgres usually beats the operational cost of a dedicated graph DB.
- Payments - ledger in relational (integrity + joins), fraud-ring detection in a graph, denormalized marts for finance reporting.
- AI / ML - training examples as documents/Parquet, embeddings in a vector DB for RAG, and a dataset→model→eval lineage graph as a governance asset.
- Quant / markets - security master in relational, tick data in columnar, surveillance/relationship graphs for market-abuse detection.
- General - catalog as documents, recommendations via graph traversal, semantic search via vectors.
Choose the model from how you'll read and relate the data: trees → document, webs you traverse → graph, ad-hoc joins with integrity → relational, semantic similarity → vector. The model shapes the thinking, so choosing wrong taxes you forever.
Storage & retrieval
Two storage-engine families sit under almost every database. Knowing which one you're on tells you your write cost, your read cost, and what breaks under load.
- Storage engine - the part of a database that actually writes bytes to disk and finds them again.
- Index - a lookup shortcut, like the index at the back of a book, so you don't scan every page.
- B-tree - keeps data sorted in fixed pages and updates them in place; how most classic databases work.
- LSM-tree - writes go to memory first, then get flushed and merged into sorted files later; great for heavy writing.
- Compaction - a background clean-up that merges those files and throws away deleted/old versions.
- Write / read / space amplification - one logical write causing several physical writes; one read touching several files; data taking extra space. You trade these against each other.
- OLTP vs OLAP - many tiny live transactions (a checkout) vs big analytical scans (revenue by region last quarter).
- Row vs column storage - store a whole record together vs store each column together. Columns win for analytics.
Two engine families
Under the hood, most storage engines are one of two shapes, and the difference is where writes go:
- B-trees keep keys sorted in fixed-size pages and update in place. Reads are shallow and predictable (a few page hops). Writes may split/merge pages and are protected by a write-ahead log. This is the classic OLTP engine (Postgres, MySQL/InnoDB, most relational databases).
- LSM-trees (Log-Structured Merge) buffer writes in an in-memory memtable, flush sorted SSTables to disk, and compact them in the background. Writes are fast sequential appends; reads may check several files (a bloom filter skips files that can't contain the key). This powers write-heavy stores (Cassandra, RocksDB, ScyllaDB, LevelDB).
The clean framing is amplification tradeoffs: B-trees have higher write amplification (in-place updates + WAL) but lower read amplification; LSM-trees have lower write amplification (sequential) but can have higher read and space amplification (multiple files, plus compaction load). Say it that way and you sound like you've run both.
OLTP vs OLAP, and why columns win analytics
OLTP (transaction processing) is many small, key-based reads and writes with low latency - row storage is ideal because you want the whole record at once. OLAP (analytics) scans huge numbers of rows but few columns (sum revenue over a year). Column-oriented storage stores each column contiguously, so a query reads only the columns it needs and compresses beautifully (adjacent values are similar). This is the foundation of every analytics warehouse.
The book leans toward "SSDs favor B-trees." Since then, research and practice (e.g., the FAST '22 work behind RocksDB tuning) refined it: on modern NVMe SSDs, well-tuned LSM engines are extremely competitive, and the real dials are compaction strategy and bloom-filter sizing, not the family choice. The bigger shift is the columnar lakehouse: Parquet/ORC files on object storage, made transactional by Apache Iceberg, Delta Lake, or Hudi - column storage plus ACID table semantics on a data lake. When you say "OLAP" in 2026, you mean this.
"LSM is always faster than B-tree." Wrong and it flags you. LSM optimizes write throughput at the cost of read and space amplification and background compaction load; B-trees give predictable low-latency reads and are often better for read-heavy transactional workloads. The correct answer names the workload first, then the engine.
"B-tree vs LSM-tree - when each?" Read-heavy, latency-sensitive OLTP → B-tree. Write-heavy ingestion (time-series, events, logs) → LSM. Follow-up: "What is compaction and what does it cost?" Background merging of SSTables to reclaim space and keep reads bounded; it competes with live traffic for disk I/O and can cause write stalls if it falls behind. Follow-up: "Why is columnar good for analytics?" Reads only needed columns, compresses adjacent similar values, enables vectorized execution.
- Pick which amplification you'll pay - write, read, and space amplification trade off; you optimize two and sacrifice one. Name the sacrifice for the workload out loud.
- Compaction is the real operational risk - size-tiered vs leveled strategy, bloom-filter bits, and write-stall behaviour under sustained writes are where LSM incidents live. Ask "what happens when compaction falls behind?" (stalls, read-amp spikes).
- OLAP performance is a layout problem - Parquet row-group sizing, partition/cluster keys, and Z-order/sort enable predicate + projection pushdown and data skipping. Most "slow query" issues are layout, not engine.
- Columnar has a cost model - small files, over-partitioning, and shuffle-heavy joins dominate spend; compaction/optimize jobs are part of the design, not an afterthought.
- Payments - high-write ledgers/event stores on LSM-backed engines; reconciliation and reporting marts on columnar (Parquet/Iceberg) for big scans.
- AI / ML - write-heavy feature/event stores on LSM; training sets as sorted/clustered Parquet for fast columnar reads during data loading.
- Quant / markets - tick stores as columnar time-series sharded by symbol + time; point lookups vs range scans drive the layout.
- General - metrics/time-series on LSM-style engines (Druid/Cassandra); BI dashboards on columnar with data skipping.
Storage engine = append-and-compact (LSM) or mutate-in-place (B-tree), and the choice is a write-vs-read-vs-space amplification trade. Layout = rows for transactions, columns for analytics. Name the workload, then the engine.
Encoding & evolution
Data outlives code. The moment you have two versions of an app running at once - which is always, during any deploy - you need a plan for how old and new data understand each other.
- Encoding / serialization - turning in-memory objects into bytes you can store or send; decoding is turning them back.
- Schema - the agreed shape of the data (fields + types). Some formats ship the schema separately.
- Backward compatible - new code can still read old data.
- Forward compatible - old code can still read new data (it ignores fields it doesn't know).
- Avro / Protobuf / Thrift - compact binary formats with schemas, used between services and in data pipelines.
- Parquet - a columnar file format for analytics - great for scanning a few columns over billions of rows.
- Schema registry - a shared service that stores schema versions and refuses changes that would break readers.
Why encoding is really about change
Encoding (serialization) turns in-memory objects into bytes for storage or transport; decoding reverses it. The interesting part is not the bytes - it's evolution. In any rolling deploy, old and new code coexist, so your formats must support:
- Backward compatibility - new code reads old data. (Usually easy: you know the old format.)
- Forward compatibility - old code reads new data. (Harder: old code must ignore fields it doesn't understand rather than choke.)
Language-native serialization (Java Serializable, Python pickle) is convenient and a trap: it's tied to one language, insecure, and versions poorly. Prefer a schema-based cross-language format.
The formats, and how they evolve
- Protocol Buffers / Thrift - fields carry numeric tags. You evolve by adding new optional fields with new tags; you never reuse or renumber a tag. Old readers skip unknown tags (forward compat); new readers treat missing fields as defaults (backward compat).
- Avro - no tags; relies on a writer's schema and a reader's schema resolved at read time. Ideal for big data files and pipelines where the schema is stored once per file. Adding/removing fields with defaults preserves both compatibility directions.
- Parquet - columnar on-disk format for analytics; encodes each column with type-aware compression. The lingua franca of the lakehouse.
Dataflow happens three ways, each with the same evolution demand: through databases (a write today is read by code years from now - beware an old reader dropping a new field on rewrite), through services (REST/gRPC - servers and clients update independently), and through async messages (queues/brokers decouple sender and receiver in time).
The Confluent Schema Registry operationalized all of this for Kafka. It stores versioned schemas (Avro/Protobuf/JSON Schema) and enforces a compatibility mode on every change, which dictates deploy order. Know this table cold:
| Compatibility mode | What it allows | Upgrade first |
|---|---|---|
BACKWARD (default) |
New schema reads old data (add optional / delete fields) | Consumers first |
FORWARD |
Old schema reads new data (add fields / delete optional) | Producers first |
FULL |
Both directions (add/remove only optional fields with defaults) | Either order |
"Backward vs forward compatibility?" Backward = new code reads old data; forward = old code reads new data. You need both because during a deploy both versions run at once. Follow-up: "How does Avro evolve without breaking?" Reader and writer schemas are resolved field-by-field; fields added with defaults stay compatible; there are no tag numbers to collide. Follow-up: "If your registry is set to BACKWARD, who deploys first?" Consumers, so the new readers can handle data still being written by old producers.
S: A core event schema consumed by N downstream services needed a new field for a product or compliance reason; no downtime was possible. T: Evolve it without breaking any consumer or risking a single mishandled record. A: Set the registry to FULL compatibility; shipped producers writing old+new fields; migrated consumers one service at a time behind monitoring; retired the old field only after confirming zero readers; reserved the field id. R: [N] services migrated with zero downtime and zero data incidents; the rollout pattern became the team's default. (Fill brackets with real numbers.)
- Schema evolution is an org problem - the registry compatibility mode is a contract between teams, and the deploy-order rule is a cross-team release policy you enforce in CI - not hope for.
- Treat schemas as owned, versioned APIs - a data contract = schema + freshness/quality SLA + an owner + a deprecation window. The classic failure is a silent producer change breaking a model days later; catch it with compat checks and contract tests at PR time.
- Know which evolutions rewrite data - in Parquet/Iceberg, add/rename/reorder via field-IDs is metadata-only, but some type and partition changes force full rewrites - know before you promise zero-downtime.
- Reproducibility is compliance - for ML, dataset/eval versioning + immutable snapshots + lineage answers "which exact data trained model v37?" - a requirement, not a nicety.
- Payments - evolving a payment-event schema across N services under FULL compatibility; PII tagged in-schema to drive masking and retention.
- AI / ML - a data contract between annotation tooling and the RLHF pipeline; versioned eval sets so a harmlessness benchmark is reproducible.
- Quant / markets - FIX / market-data message evolution where forward-compat lets running strategies ignore new fields safely.
- General - gRPC/Protobuf service contracts; Avro-on-Kafka with a registry gating breaking changes in CI.
Encoding is about change over time. Old and new code always coexist, so demand backward + forward compatibility, use a schema format (Avro/Protobuf) plus a registry, and let the compatibility mode dictate deploy order.
Replication
Replication keeps a copy of the same data on multiple machines - for lower latency (data near users), higher availability (survive failures), and read throughput. The entire difficulty is what happens to writes.
- Replication - keeping the same data on several machines so one can die without data loss.
- Leader / follower - one machine takes all the writes (the leader) and copies them to the others (followers). Also called primary/replica.
- Synchronous vs asynchronous - wait for the copy to confirm before saying "done" (safe, slower) vs don't wait (fast, can lose the last writes).
- Replication lag - the short delay before a follower catches up to the leader.
- Failover - promoting a follower to leader when the leader dies.
- Quorum - requiring a majority to agree before a read or write counts.
- Eventual consistency - replicas may briefly disagree but converge if writes stop.
Three architectures
- Single-leader - all writes go to one leader, which streams a replication log to followers; reads can be served by any replica. Simple and consistent, but the leader is a write bottleneck and a failover risk. This is default Postgres/MySQL replication.
- Multi-leader - multiple nodes accept writes (e.g., one per datacenter). Better write availability and locality, but write conflicts become possible and must be resolved. Use only when you truly need multi-region writes or offline clients.
- Leaderless (Dynamo-style, as in Cassandra/Riak) - any replica takes writes; clients read from and write to several nodes and use quorums to stay consistent. Highly available and partition-tolerant; consistency is tunable, not automatic.
Sync vs async, and the quorum rule
Synchronous replication waits for a follower to confirm (no data loss on failover, but a slow/stuck follower stalls writes). Asynchronous is fast but can lose recently-acked writes if the leader dies before they propagate. Most systems are semi-sync (one synchronous follower, the rest async).
For leaderless systems, the consistency knob is the quorum inequality: with N replicas, W write-acks, and R read-replies, if W + R > N you're guaranteed to read at least one node that saw the latest write. Tuning W and R trades durability, read/write availability, and latency.
Replication lag creates read-after-write anomalies. The consistency guarantees you can offer: read-your-writes (you always see your own updates), monotonic reads (you never see time go backward across reads), and consistent-prefix reads (you see writes in causal order).
The frontier moved to consensus-replicated distributed SQL: Spanner, CockroachDB, and YugabyteDB replicate each shard with Raft/Paxos to give you strong consistency and horizontal scale - the thing the book treats as a hard tradeoff, engineered into a product. On the operational side, managed services (Aurora, AlloyDB) separated compute from storage so replicas share a distributed storage layer, shrinking replication lag. When someone asks "how do you get strong consistency across regions," the answer is a consensus-replicated store, and you should know it costs you write latency (a majority round-trip).
"Synchronous vs asynchronous replication - the tradeoff?" Sync = no data loss but higher latency and a stall risk if a replica is slow; async = low latency but possible data loss on failover. Follow-up: "What's the W + R > N rule?" A quorum condition guaranteeing read/write sets overlap so reads see the latest write. Follow-up: "A user posts a comment then refreshes and it's gone - what's wrong and how do you fix it?" Read-after-write violation from replication lag; route a user's own reads to the leader (or a synced replica), or read by a version token, until the write has propagated.
- Design to an explicit RPO/RTO - synchronous = zero data loss but higher latency and availability risk; async = fast but you can lose the last writes on failover. "How much data can we lose on a leader failure?" is the question.
- Failover is where outages are made - split-brain, lost writes, and reconnect storms. Fence it, guard the automation, and test it in game days - untested failover is a latent outage.
- Read replicas don't scale writes - and they create read-after-write hazards; centralize read-your-writes routing (leader sticky reads or version tokens) rather than reinventing it per service.
- Multi-region is a deliberate PACELC choice - active-active needs conflict resolution (CRDTs / LWW / app merges); prefer active-passive unless you genuinely need local writes.
- Payments - synchronous replication for the ledger (no lost transactions), async replicas for dashboards, active-passive multi-region DR with a tested runbook.
- AI / ML - read replicas/caches for low-latency feature serving; eventual consistency is fine for analytics, not for usage metering/billing.
- Quant / markets - co-located low-latency replicas of reference data; strict ordering for anything feeding execution.
- General - primary + standby Postgres with sticky read-your-writes; CockroachDB/Spanner when you need strongly-consistent regional writes.
Single-leader = simple, write-bottlenecked. Multi-leader = available but conflict-prone (avoid unless geo). Leaderless = highly available with tunable W + R > N quorums. Replication lag is why "read your own writes" needs deliberate design.
Partitioning (sharding)
When data is too big for one machine, you split it across nodes. The whole game is choosing a partition key that spreads load evenly - and handling the keys that refuse to cooperate.
- Partitioning / sharding - splitting a big dataset across machines so each holds a slice.
- Partition key - the field you split on (e.g., user ID). It decides which machine holds a row.
- Range partitioning - slice by ranges (A-F here, G-M there). Great for "give me everything between two dates."
- Hash partitioning - scramble the key with a hash so rows spread evenly, at the cost of easy range scans.
- Hot spot / hot key - one slice getting way more traffic than the others (a celebrity user, a viral product).
- Rebalancing - moving data around when you add or remove machines.
- Consistent hashing - a way to place data so adding a node moves as little data as possible.
Range vs hash
- Range partitioning keeps keys sorted and splits into contiguous ranges. Range scans are efficient (time-series "last 24 hours" lands in one place), but sequential keys create hot spots - if the key is a timestamp, all writes hit the newest partition.
- Hash partitioning applies a hash to the key, spreading writes evenly and killing that hot spot - but you lose efficient range scans (adjacent keys scatter everywhere). Cassandra's compromise: a compound key that hashes a partition key but sorts rows within the partition, giving you both.
The hot-key problem
Even with hashing, a single key can be too popular (a celebrity's account, one mega-tenant). Fixes: add a random suffix to split the hot key across partitions (salting) and gather results on read; cache in front; or give heavy tenants dedicated partitions. Naming this problem unprompted signals real distributed-systems experience.
Rebalancing and routing
When you add nodes, data must move without a full reshuffle. Fixed partition counts (make many more partitions than nodes, move whole partitions) and consistent hashing both limit movement. A routing layer (a coordinator, or a metadata service like ZooKeeper, or gossip) tracks which partition lives where. Secondary indexes are either local (per-partition, scatter-gather reads) or global (term-partitioned, faster reads but writes touch multiple partitions).
A precise point that impresses interviewers: consistent hashing as popularized by Dynamo has known load-balancing weaknesses, and several modern systems moved away from it. Cassandra uses many virtual nodes per physical node to smooth distribution; ScyllaDB explicitly moved to newer sharding for better balance. Meanwhile the lakehouse made partitioning declarative: Iceberg hidden partitioning and partition evolution let you change the partition scheme without rewriting the table or breaking queries. So "consistent hashing" is a starting point, not the final answer.
"Range vs hash partitioning?" Range = efficient scans, risk of sequential hot spots; hash = even spread, no efficient range scans. Follow-up: "You have a hot partition - how do you fix it?" Salt the hot key with a suffix to spread it and scatter-gather on read; add a cache; isolate heavy tenants. Follow-up: "How do you rebalance without downtime?" Pre-split into many fixed partitions and move whole partitions, or use consistent hashing / vnodes; keep a routing layer that tracks placement.
S: A small number of high-volume keys (a few dominant tenants) were overloading a single partition, hurting tail latency for everyone. T: Even out load without a full re-architecture. A: Introduced a salted compound key for the heaviest keys, added a read-path cache, and changed the partitioning so heavy tenants split across nodes; validated with p99 dashboards before/after. R: p99 dropped from [X] to [Y]; cluster utilization evened out; capacity reclaimed. (Fill brackets.)
- The partition key is the hardest call to reverse - design for the dominant query and even write distribution, and plan a re-partition path (Iceberg partition evolution or a documented re-shard) from day one.
- Hot partitions are a modeling failure - not a capacity one; adding nodes won't help one hot key. Know the toolkit (salting + scatter-gather, write-sharding time-ordered keys, caching, hedging) and the read-amp each costs.
- Rebalancing is an availability event - data movement competes with live traffic for IO and can cascade into latency; throttle it, schedule it, and watch the tail during it.
- Index placement scales differently - global/term-partitioned indexes become a write bottleneck; local/document-partitioned indexes make reads scatter-gather. Choose by your read/write ratio.
- Payments - partition transactions by (merchant-bucket, time) to localize a merchant's data while salting mega-merchants to spread their write hot spot.
- AI / ML - partition training/event data by date for cheap time-range backfills; avoid skew from a few enormous sources throttling a pipeline.
- Quant / markets - shard tick data by symbol + time-bucket so a symbol's range scans hit one partition; sub-partition hot symbols like index futures.
- General - choose Kafka partition keys so per-key ordering holds without one celebrity key overloading a broker.
Partition to spread load; choose range for scans, hash for evenness, compound keys for both. The real test is the hot key: salt it, cache it, or isolate it. Consistent hashing is a starting point, not the last word.
Transactions
A transaction is a promise: a group of operations either all happen or none do, and concurrent ones don't corrupt each other. The subtlety lives entirely in isolation levels - what anomalies each one still permits.
- Transaction - a bundle of steps treated as one all-or-nothing unit (move money: debit one account, credit another - both or neither).
- ACID - Atomic (all-or-nothing), Consistent (rules stay true), Isolated (concurrent ones don't step on each other), Durable (once saved, stays saved).
- Isolation level - how strictly the database keeps simultaneous transactions from seeing each other's half-done work.
- Dirty read - reading someone else's uncommitted change that might get undone.
- Race condition - two transactions interleaving in a way that produces a wrong result.
- Deadlock - two transactions each waiting for the other to let go; neither can proceed.
- Optimistic vs pessimistic - assume no conflict and check at commit vs lock upfront to prevent conflict.
ACID, precisely
Atomicity - on failure, partial writes roll back (all-or-nothing). Consistency - the app's invariants hold (this one is really the application's job). Isolation - concurrent transactions don't interfere; the gold standard is serializable (the result equals some serial order). Durability - once committed, data survives crashes. The word doing the real work is Isolation.
Isolation levels and the anomalies they allow
Stronger isolation prevents more anomalies but costs performance. Know this ladder cold:
| Level | Prevents | Still allows |
|---|---|---|
| Read Committed | Dirty reads & dirty writes | Non-repeatable reads, lost updates, write skew |
| Snapshot / Repeatable Read | + Non-repeatable reads (reads from a consistent snapshot via MVCC) | Write skew, phantoms |
| Serializable | + Everything (result = some serial order) | Nothing - but costs throughput |
Key anomalies to name: dirty read (seeing uncommitted data), non-repeatable read (a row changes value between two reads in the same transaction), lost update (two read-modify-writes clobber each other), phantom (a row appears/disappears affecting a range query), and write skew.
Write skew - the one people miss
Write skew is the anomaly that separates a strong
answer from a great one. Two transactions each read the same data,
each make a decision that is valid on its own, and together they
violate an invariant. Classic example: two on-call doctors each
check "at least one other doctor is on call," see that the other is,
and both remove themselves - now nobody is on call. Snapshot
isolation does not prevent it. Fixes:
serializable isolation (Serializable Snapshot
Isolation detects the conflict and aborts one), or explicit locking
(SELECT ... FOR UPDATE) to materialize the conflict.
Serializable Snapshot Isolation (SSI) went
mainstream: PostgreSQL's SERIALIZABLE uses it to give
true serializability at close to snapshot-isolation cost by
detecting conflicts optimistically and aborting losers.
Distributed SQL (CockroachDB,
Spanner, FoundationDB) now
offers serializable isolation across shards -
historically thought impractical. Note the naming trap: many
databases' default is READ COMMITTED, and several
call snapshot isolation "REPEATABLE READ." Always ask what a
system's default actually is.
"Explain the isolation levels." Read committed (no dirty reads) → snapshot/repeatable read (consistent snapshot, no non-repeatable reads) → serializable (no anomalies). Follow-up: "What is write skew and which level stops it?" Two transactions read-decide-write on overlapping data and jointly break an invariant; only serializable (or explicit locks) prevents it - snapshot isolation does not. Follow-up: "How does Postgres give serializable cheaply?" SSI: optimistic conflict detection that aborts a transaction if serializability would be violated.
- Choose isolation per-operation - isolate the exact read-then-decide invariants that need SERIALIZABLE (or row locks) and leave the rest cheaper; serializable everywhere is a contention disaster.
- Know your engine's real semantics - "REPEATABLE READ" differs in Postgres vs MySQL; Postgres SERIALIZABLE is SSI (optimistic, aborts under contention) - so the serialization-failure retry loop with backoff is part of the design.
- Avoid distributed transactions - 2PC blocks participants on coordinator failure; prefer aggregate boundaries that keep transactions single-shard, or sagas + idempotency + compensation across services.
- Short transactions, push invariants down - long transactions and hot rows cause lock contention, bloat, and deadlocks. Enforce rules with constraints / unique indexes / conditional updates instead of app-level check-then-write.
- Payments - SERIALIZABLE (or SELECT … FOR UPDATE) on balance-check-then-debit; idempotency key + unique constraint to make double-submit safe; sagas for multi-service settlement.
- AI / ML - a transactional outbox to atomically record a job and emit its event; model/dataset registries need real transactions, data lakes generally don't.
- Quant / markets - atomic, strictly-ordered position/risk updates; optimistic concurrency on shared reference data.
- General - inventory/seat-booking via conditional "decrement-if-available" updates; wallet and credit systems.
Transactions turn messy partial failures into all-or-nothing. Read committed stops dirty reads; snapshot isolation stops non-repeatable reads but not write skew; serializable stops everything at a throughput cost. Knowing write skew is the tell.
The trouble with distributed systems
This chapter is a catalog of ways distributed systems betray your intuitions. The lesson: build on explicit, pessimistic assumptions, because the network, clocks, and processes will all lie to you.
- Partial failure - in one big computer, things work or don't; across many, some parts fail while others keep going - and you can't easily tell which.
- Network partition - the network splits so some machines can't talk, even though all are alive.
- Unreliable clocks - each machine's clock drifts slightly, so "what time is it" disagrees across machines.
- Process pause - a program freezes for a moment (often garbage collection) and wakes up thinking no time passed.
- Timeout - giving up waiting for a reply; you can't tell "it failed" from "it's just slow."
- Fencing token - an always-increasing number that lets a resource reject a stale request from a paused-then-woken node.
The network is not reliable
Messages can be lost, delayed, duplicated, or reordered, and when you send a request and hear nothing, you cannot distinguish "the request was lost," "the node is down," "the response was lost," or "everything is fine but slow." Your only tool is a timeout, and choosing its length is a genuine tradeoff (too short → false positives and duplicate work; too long → slow failure detection). This ambiguity is the root of most distributed bugs.
Clocks are not trustworthy
Time-of-day clocks (wall clock) can jump backward when NTP corrects them - never use them to order events. Monotonic clocks only move forward and are for measuring durations, not timestamps. The deep point: you cannot rely on synchronized clocks for correctness. Using a timestamp to decide "last write wins" silently loses data when clocks disagree. Systems that need real ordering use logical clocks (Lamport/vector) or specialized time (below).
Processes pause, and knowledge is local
A process can pause arbitrarily - GC, VM migration, swapping - and not know it paused. So a node that believes it's still the leader may have been dead (from everyone else's view) for seconds. This breaks locks: a node acquires a lease, pauses, the lease expires, another node takes over, then the first wakes up and acts as if it still holds it. The fix is a fencing token: the lock service hands out a monotonically increasing number with each grant, and the protected resource rejects any request carrying a token lower than the highest it has seen. A single node's view of the system is never authoritative - only a quorum's is.
The industry's answer to unreliable clocks is to engineer better ones. Google Spanner's TrueTime uses GPS and atomic clocks to bound clock uncertainty and waits out that interval to give globally-consistent commit timestamps. AWS built the same idea into the Amazon Time Sync Service with microsecond accuracy. On the failure-injection side, chaos engineering (Chaos Monkey, Gremlin, Litmus) became standard practice: deliberately inject partitions, latency, and node death so your fencing and failover are tested before production tests them.
"Your request times out. What do you actually know?" Almost nothing - the request may have succeeded, failed, or be in flight; the response may be lost. So operations must be idempotent and retried safely. Follow-up: "Why not use timestamps to resolve write conflicts?" Clock skew makes last-write-wins drop data; use logical clocks or version vectors. Follow-up: "A GC pause makes two nodes think they're the leader - how do you prevent bad writes?" Fencing tokens: monotonically increasing numbers the resource checks, rejecting stale holders.
- Make everything idempotent and bounded - assume calls can be slow, lost, duplicated, or reordered; give every retry an explicit budget with jitter and circuit breakers to prevent retry storms.
- Beware metastable failure - a transient blip + aggressive retries can lock a system into a degraded state that outlives the trigger. Load shedding and backpressure are reliability features, not extras.
- Never trust wall-clocks for correctness - ordering or expiring on cross-machine timestamps (LWW conflict resolution, lease expiry) is a latent data-loss bug; use logical clocks/versions and fencing tokens.
- Health checks lie - a node can pass health checks while GC-paused; build liveness on quorum + fencing and treat "am I still the leader?" as never locally answerable.
- Payments - idempotency keys + reconciliation exactly because "no response" is ambiguous - the charge may have succeeded; never assume failure on timeout.
- AI / ML - distributed training with checkpoint/restart for stragglers and node death; idempotent data loading so a retried shard doesn't duplicate.
- Quant / markets - timeouts + fencing on order gateways; clock discipline (PTP/GPS) where cross-host event ordering matters for sequencing/surveillance.
- General - distributed locks via fencing tokens (etcd/Redis); circuit breakers + exponential backoff with jitter on every external call.
Networks lose messages, clocks lie, processes pause - and a node can't even trust its own status. Build on timeouts + retries + idempotency, order with logical clocks not wall clocks, and protect shared resources with fencing tokens.
Consistency & consensus
This is the theoretical peak of the book and the most common source of "senior" screening questions. Three words get used interchangeably by amateurs and precisely by experts: linearizability, serializability, causal consistency.
- Consistency (here) - the rules about whether everyone sees the same data at the same time, and in what order.
- Linearizability - the system behaves as if there's one single copy of the data, updated instantly; once you write, everyone immediately sees it.
- Serializability - about transactions: the end result matches running them one-at-a-time in some order.
- Causal consistency - everyone sees cause before effect (you never see a reply before the message it answers), but unrelated things can differ.
- Consensus - getting several machines to agree on one value even when some fail.
- Raft / Paxos - the standard recipes for reaching consensus.
- CAP - an often-misquoted rule of thumb about consistency vs availability when the network splits.
Three words, kept straight
- Linearizability is a recency guarantee about single objects: the system behaves as if there is one copy and every operation takes effect atomically at some point between its start and end. Once a write completes, every later read sees it. It makes a distributed system look single-node.
- Serializability is an isolation guarantee about transactions: the outcome equals some serial execution. It says nothing about real-time recency.
- Causal consistency preserves the order of causally-related operations (cause before effect) but lets concurrent, unrelated operations be seen in different orders. It's the strongest model that stays available under partition, which makes it the sweet spot for many systems.
Being able to state these three distinctly - and say "linearizability is about single-object recency, serializability is about transaction ordering, they are different guarantees" - is exactly the senior signal interviewers listen for.
Consensus and total order
Consensus is getting nodes to agree on a value despite failures, and it turns out many problems are equivalent to it: leader election, distributed locks, atomic commit, uniqueness constraints. Raft (used by etcd, CockroachDB, Consul) and Paxos/Multi-Paxos (used by Spanner, Chubby) implement it by electing a leader and replicating an ordered log to a majority quorum. Consensus gives you total order broadcast - every node sees the same operations in the same order - which is the backbone of strongly-consistent systems.
What CAP actually says
CAP is narrow: when a network partition occurs, you must choose between consistency (linearizability) and availability. It says nothing when there's no partition, and it ignores latency entirely. The modern, more useful framing is PACELC: if Partitioned, trade Availability vs Consistency; Else, trade Latency vs Consistency. That "else" clause is where real systems live every day.
Kyle Kingsbury's Jepsen testing became the industry's truth-teller: it partitions real databases and checks their consistency claims, and "passes Jepsen" is now a credibility marker. PACELC is the framing sophisticated engineers use instead of bare CAP. And consensus is no longer exotic - it's embedded in etcd (which underpins Kubernetes), CockroachDB, Consul, and TiDB, so "the system uses Raft under the hood" is a sentence you'll say often.
"Linearizability vs serializability?" Linearizability = real-time recency on single objects (behaves like one copy); serializability = transactions produce some serial-order result. Different guarantees; a system can have one without the other. Follow-up: "When do you actually need linearizability?" Leader election, locks, uniqueness - anything where staleness breaks correctness. Follow-up: "State CAP precisely." Under a partition, choose consistency or availability; otherwise CAP is silent - use PACELC, which adds the latency-vs-consistency tradeoff in the normal case.
"CAP means pick two of consistency, availability, partition tolerance." That's the pop-science version and it flags you. Partition tolerance isn't optional in a distributed system - partitions happen whether you like it or not - so the real choice is C vs A during a partition. Quote PACELC and you instantly sound more current.
- Use the weakest model that holds the invariant - strong consistency is a latency/availability tax (PACELC); reserve linearizability for locks, leader election, uniqueness, and money - use causal/eventual elsewhere.
- Cost the consensus - every linearizable write is a majority round-trip; cross-region adds tens of ms. This dictates data placement - keep the consensus group regional and the leader near the writers.
- Interrogate "strong consistency" claims - what scope: single key, single shard, cross-shard, under failover? Spanner leans on TrueTime, Cockroach differs; vendors blur it. Ask what holds across shards and regions.
- Consensus systems are tier-0 failure domains - etcd/ZK quorum loss takes down everything that depends on them (scheduling, discovery); size, isolate, and monitor them accordingly.
- Payments - linearizable uniqueness on idempotency keys and account creation; causal/eventual is fine for activity feeds and analytics.
- AI / ML - a strongly-consistent metadata/registry for "which dataset/model is current"; bulk training data needs no such guarantee.
- Quant / markets - linearizable sequencing for an order book / matching engine; relaxed consistency downstream for analytics.
- General - leader election and config via etcd/ZK/Consul; distributed locks; "exactly one scheduler" guarantees.
Linearizability = behaves like one copy, with real-time recency (needed for locks, leader election, uniqueness). Serializability = transactions in some serial order. Causal = cause-before-effect, stays available under partition. Consensus (Raft) gives total order. Quote PACELC, not just CAP.
Batch processing
Batch processing takes a large, bounded dataset, runs a job, and produces an output. Its real gift is a philosophy - immutable inputs and deterministic functions - that makes large-scale data both debuggable and fault-tolerant.
- Batch processing - crunch a big pile of data all at once and produce a result (last night's full sales report).
- Bounded data - a dataset with a known start and end (it doesn't keep growing while you read it).
- MapReduce - an old but foundational recipe: "map" each record in parallel, then "reduce" (combine) the grouped results.
- Immutable input - the source data is never changed, so you can safely re-run a job as many times as you like.
- Deterministic - same input always gives the same output; no randomness, no clock-dependence.
- Idempotent - running it twice does no more harm than running it once.
- Shuffle - the expensive step of moving data across the network so related records end up together.
- Skew - one group being far bigger than the others, so one worker becomes the bottleneck.
The MapReduce philosophy (which outlived MapReduce)
MapReduce itself is largely historical, but its principles are the foundation of all modern data processing, so state them as principles:
- Immutable inputs - a job never modifies its source; it reads input and writes new output. So you can re-run freely.
- Deterministic functions - the same input always yields the same output, which is what makes retries safe.
- Retryable / idempotent tasks - because of the two above, a failed task is just re-run on another node; this is how batch systems get fault tolerance almost for free.
Mechanically, map transforms each record in parallel; a shuffle groups records by key across the network (the expensive part); reduce aggregates each group. The join strategies you should name: sort-merge join (shuffle both sides by key), broadcast hash join (ship a small table to every mapper), and partitioned hash join.
Why Spark replaced MapReduce
MapReduce writes intermediate results to disk between every step. Spark models the whole job as a DAG of transformations over datasets that can stay in memory, so multi-step pipelines avoid the disk round-trips, and it recomputes lost partitions from lineage instead of re-running everything. That's the entire reason it won: same philosophy, far less I/O.
Batch didn't disappear, it moved up the stack. Spark is the default engine; the interface is increasingly plain SQL, orchestrated by dbt for transformation and Airflow / Dagster / Prefect for scheduling. Storage is the lakehouse (Iceberg / Delta / Hudi on object storage). The line between batch and streaming is blurring: engines like Spark Structured Streaming and Flink run the same logic over both, so "batch vs streaming" is now often "big interval vs small interval." The philosophy - immutable inputs, deterministic transforms, recomputable outputs - is exactly why the lakehouse can time-travel and reprocess.
"Why is immutability so useful in batch systems?" Immutable inputs make jobs retryable and idempotent, so fault tolerance is just re-running failed tasks, and you can reprocess history any time. Follow-up: "Why did Spark beat MapReduce?" In-memory DAG execution avoids writing intermediate results to disk between steps and recomputes lost partitions via lineage. Follow-up (OpenAI): "Backfill 3 years of data while the live pipeline runs - how?" Reprocess partitioned immutable input into a separate output, keep transforms deterministic and idempotent, write to a staging table, then atomically swap - so the backfill never corrupts live data.
- Idempotent, partitioned, restartable - design so any partition reprocesses independently and re-running is safe (overwrite-by-partition or upsert-by-key) - this is what makes backfills and bug-fixes routine instead of terrifying.
- Skew and shuffle dominate cost - one giant key makes a single task the long pole; the levers are salting, map-side combine, broadcast vs shuffle joins, and right-sized partitions. "Why is this Spark job slow/expensive?" is almost always skew, shuffle, or small files.
- Orchestrate for lineage + partial reruns - asset-aware tooling (Dagster) with data-aware retries, backfill ranges, and dependency scheduling beats cron.
- Don't stream what micro-batch satisfies - pick batch vs streaming by the real freshness SLA and cost; streaming infra carries a genuine operational tax.
- Payments - nightly reconciliation / "recompute the universe" from immutable events; backfilling a corrected fee across 3 years via partitioned, idempotent reprocessing.
- AI / ML - large-scale dataset construction, dedup, and filtering in Spark; generating training shards; periodic full feature recompute for correctness.
- Quant / markets - end-of-day analytics, factor recomputation, multi-year backtests over partitioned tick history.
- General - ELT into the warehouse via dbt; big joins/aggregations; rebuilding a search index from source.
Batch = bounded input, throughput-first, with immutable inputs + deterministic functions + recomputable outputs. That trio buys fault tolerance and safe reprocessing. Spark won by keeping the philosophy and cutting the disk I/O.
Stream processing
Streaming handles unbounded data - events that never stop arriving - and processes each shortly after it happens. It's the most operationally relevant chapter for a modern data engineer, and the richest source of hard interview follow-ups.
- Stream - data that keeps arriving forever, handled a bit at a time (a live feed of clicks or payments).
- Event - one thing that happened, with a timestamp ("user X clicked at 10:00:03").
- Event time vs processing time - when it actually happened vs when your system got around to handling it. They differ because of delays.
- Watermark - a marker that says "we've probably seen all events up to time T now," so you can safely finish a time window.
- Windowing - grouping events into time buckets (per-minute counts).
- At-least-once / exactly-once - never lose an event but maybe double-count vs count each event's effect exactly one time.
- CDC (Change Data Capture) - turning every change in a database into a stream of events.
- Backpressure - when a fast producer must slow down because a downstream step can't keep up.
Event time vs processing time
The defining problem of streaming: an event's event time (when it happened) differs from its processing time (when you handle it), because of network delays, retries, and offline devices. If you bucket by processing time, your "per-minute" counts are wrong whenever data is late. So streaming systems work in event time and need a way to decide when a window is "done."
That mechanism is the watermark: a moving assertion that "all events with timestamp ≤ T have (probably) arrived," which lets you emit a window's result. Late events after the watermark are handled by a policy (drop, side-output, or update). Windowing comes in tumbling (fixed, non-overlapping), hopping/sliding (overlapping), and session (activity-bounded) flavors.
Delivery guarantees, precisely
- At-most-once - fire and forget; may lose events. Rarely acceptable.
- At-least-once - retry until acked; never loses but may duplicate. The common default.
- Exactly-once - each event affects state once. Not magic - it's engineered from an idempotent/transactional producer, transactional state, and an idempotent or transactional sink (details in Part V-3).
Change Data Capture and the log
CDC turns a database's write log into a stream: every insert/update/delete becomes an event other systems consume. This is how you keep a search index, cache, or warehouse in sync with a source database without dual writes. It rests on the book's central image - the log (an append-only, ordered sequence) as the source of truth, with every derived store as a consumer that can be rebuilt by replaying it.
Apache Flink became the leader for true event-at-a-time streaming with robust event-time and exactly-once support; Kafka Streams and Spark Structured Streaming cover large shares too. Debezium is the de facto CDC tool, streaming database changes into Kafka. The Kappa architecture (do everything as streams, replay the log to reprocess) emerged as a rival to Lambda (separate batch + speed layers). And streaming SQL (Flink SQL, ksqlDB, Materialize) lets you express streaming joins and aggregations declaratively. This chapter is essentially a description of a modern real-time data platform.
"Event time vs processing time - why does it matter?" Correct time-based aggregations must use event time; processing time makes counts wrong whenever data is delayed. Follow-up: "What's a watermark?" A heuristic marking that all events up to time T have likely arrived, so a window can close; late events get a defined policy. Follow-up: "How do you actually achieve exactly-once?" Idempotent/transactional producer + checkpointed transactional state + idempotent or transactional sink; end-to-end, not one component. Follow-up: "How do you sync a DB to a search index without dual writes?" CDC (Debezium) streaming the change log into the index.
S: A high-value event stream (billing, payments, or ordering) risked double-processing on retries, with a dual-write gap between the operational store and the downstream queue. T: Guarantee each event is reflected exactly once, with a full audit trail. A: Introduced deterministic idempotency keys, an outbox written in the same transaction as the business change, CDC to publish reliably, and idempotent upserts at the sink; verified with reconciliation that source and derived counts matched. R: Eliminated duplicate/missed events ([metric]); reconciliation discrepancies fell to ~0; the pattern was reused across pipelines. (Fill brackets.)
- The hard parts are state, time, and delivery - size operator state and its checkpoint/restore (RocksDB backend, checkpoint interval, savepoints for upgrades); set a watermark + allowed-lateness + side-output policy; pick the delivery guarantee the business actually needs.
- Scope exactly-once deliberately - it costs latency + throughput and needs an idempotent/transactional sink end-to-end. Put EOS on the money path; use at-least-once + idempotent upserts elsewhere - and be able to explain the whole chain.
- Backpressure and stateful rescaling are real - a slow sink stalls the pipeline; key skew creates hot operators; upgrading a stateful job needs savepoints. Plan replay (Kappa) from the log to rebuild state/views.
- The dual-write trap is everywhere - transactional outbox + CDC is the default answer; consumers stay idempotent because outbox is at-least-once, not exactly-once.
- Payments - exactly-once payment processing with outbox + CDC + idempotent upserts; sliding-window fraud scoring; reconciliation as a correction stream.
- AI / ML - streaming RLHF preference/eval events into the lake; online feature computation feeding low-latency serving; near-real-time data-drift detection.
- Quant / markets - real-time market-data normalization, windowed VWAP/feature computation, exactly-once trade and PnL event processing.
- General - CDC keeping a search index / cache / warehouse in sync; clickstream sessionization; windowed metrics and eventing.
A stream is an immutable event log; databases and features are materialized views of it. Work in event time, close windows with watermarks, choose your delivery guarantee deliberately, and sync systems with CDC instead of dual writes.
The future: unbundling the database
The book's closing argument is the intellectual key to the entire modern data stack: stop thinking of "the database" as one monolith, and start thinking of a log of events as the source of truth, with every store around it a derived, rebuildable view.
- Unbundling the database - instead of one product doing storage + indexing + caching + analytics, use a stream of changes to feed several specialized tools, each doing one job well.
- Source of truth - the one place that's always correct; everything else is a copy derived from it.
- Materialized view - a precomputed, ready-to-read result that's kept up to date as new data arrives.
- Dataflow - data automatically flowing from the log into each derived system that needs it.
- Rebuildable - if a derived store is wrong or new, you recreate it by replaying the log from the start.
The core move
A traditional database bundles several things: durable storage, indexes for reads, materialized/cached views, and replication. Kleppmann's thesis is to unbundle them and let a stream of change events be the connective tissue. The write-ahead log becomes the source of truth; every other system - a search index, a cache, an analytics warehouse, an ML feature store - is a materialized view derived from that log, and each can be rebuilt by replaying it.
This reframes data engineering itself: your job is to build and maintain reliable dataflows that turn source-of-truth events into the many derived representations the business needs, keeping them correct and rebuildable.
Why this is the whole modern stack
Once you see it, the current ecosystem is just this idea implemented: Kafka as the central log; CDC/Debezium turning databases into event streams; Flink maintaining derived views; the lakehouse (Iceberg/Delta) as rebuildable derived tables with time travel; feature stores as materialized views for ML; vector databases as derived semantic indexes. All of them are "a view derived from the log."
The industry built exactly what the chapter imagined. Data mesh turned "derived views owned by teams" into an organizational principle (data as a product). Streaming lakehouses unify the log and the tables. Reverse ETL pushes warehouse-derived data back into operational tools - dataflow closing the loop. Feature stores, vector indexes, and materialized-view engines (Materialize, RisingWave) are all "the database, unbundled." If you can say "the modern data platform is the unbundled database with the log as its spine," you're demonstrating the synthesis senior interviewers want.
"What does 'unbundling the database' mean?" Treat an event log as the source of truth and every store (index, cache, warehouse, feature store, vector DB) as a materialized view derived from it and rebuildable by replay. Follow-up: "Why is that powerful?" You add new derived systems without touching the source, reprocess history to fix bugs, and get a clean audit trail. Follow-up: "How does this map to tools you'd use?" Kafka = log; Debezium = CDC; Flink = view maintenance; Iceberg/Delta = rebuildable tables; feature/vector stores = specialized views.
- It's a decision to own the guarantees yourself - you gain flexibility (any derived store is a new log consumer) but inherit idempotency, ordering, schema/lineage, and reprocessing across systems - a real platform investment with headcount cost.
- Know when NOT to unbundle - if one strongly-consistent database (or distributed SQL) meets scale and latency, keeping guarantees in one box is simpler and safer. Unbundle for many heterogeneous reads, independent scaling, or hard org boundaries.
- The log becomes your tier-0 asset and coupling point - schema governance on the event log, retention/compaction policy, and "can we replay from time T?" are platform concerns; a catalog and lineage stop being optional.
- Replay is the superpower and the trap - it fixes bugs by recomputing views, but needs deterministic transforms + idempotent sinks, and replaying a high-volume log has real cost/time - design it before you need it.
- Payments - events as source of truth with derived ledgers, search, dashboards, and features; reconciliation = replay from the log.
- AI / ML - raw events as the log; datasets, features, and embeddings as versioned materialized views; rebuild an eval/feature set by replay when logic changes.
- Quant / markets - an immutable tick/event log feeding multiple derived stores (real-time + historical); backfill by replay into Iceberg.
- General - CDC + Kafka + Flink + lakehouse + vector/search as one architecture; CQRS read models rebuilt from the log.
Unbundle the database: make the event log the source of truth and every store a rebuildable materialized view of it. This one idea is the blueprint for the entire modern data stack - and the synthesis that makes a senior answer sound senior.
The contested six, in depth
CAP is a starting point; PACELC is the adult version
CAP says only this: during a network partition, a system must choose consistency (linearizability) or availability. It's silent when there's no partition and ignores latency entirely, which is why quoting it alone sounds dated. PACELC completes it: if Partitioned, trade Availability vs Consistency; Else, trade Latency vs Consistency. Every strongly-consistent distributed store pays a latency cost on every write (a majority round-trip) even when nothing is wrong - that's the "else" clause in daily life.
Say this: "Partition tolerance isn't optional, so the real choice is C-vs-A during a partition; and PACELC adds the everyday latency-vs-consistency tradeoff CAP ignores."
LSM vs B-tree, with the nuance that dates most candidates
B-trees update in place: predictable low-latency reads, higher write amplification (WAL + page writes), the classic OLTP choice. LSM-trees append to memory then compact: high write throughput and good compression, at the cost of read amplification (check multiple SSTables, mitigated by bloom filters) and background compaction load. The dated take is "SSDs always favor B-trees." The current nuance: on modern NVMe, well-tuned LSM engines (post-FAST '22 RocksDB work) are highly competitive, and the real levers are compaction strategy and bloom-filter sizing, not the family label.
Say this: "Name the workload first: read-latency-sensitive OLTP leans B-tree; write-heavy ingestion leans LSM. On NVMe the gap narrows and tuning dominates."
Exactly-once, end to end - the full mechanism
Exactly-once is not a checkbox; it's four cooperating parts.
(1) Idempotent producer - each record carries a
producer id + sequence number so the broker dedups retries
(Kafka's idempotent producer).
(2) Transactions across the read-process-write cycle
- the consumer offset commit and the output write happen in one
atomic transaction, and downstream readers use
read_committed so they never see aborted output.
(3) Checkpointed state - the stream processor
snapshots operator state and input positions atomically (Flink's
distributed checkpoints), so on recovery it resumes from a
consistent point.
(4) Idempotent or transactional sink - the final
write is deduplicated by key or committed transactionally (a
2-phase-commit sink), so replays don't double-apply. Cost: roughly
a few extra milliseconds of latency and ~10-20% throughput.
Reference: Uber runs exactly-once event processing on Flink +
Kafka with upserts into Pinot.
Say this: "Exactly-once is idempotent producer + transactional read-process-write + checkpointed state + idempotent/transactional sink - end to end, or it isn't exactly-once."
The transactional outbox pattern
The dual-write problem: your service updates a
database and publishes an event to a queue as two
separate operations, and if it crashes between them, the two
disagree forever. The outbox pattern fixes it: in
the same local transaction as the business change, insert
the event into an outbox table. A separate process
(usually CDC via Debezium reading the DB log)
publishes those rows to Kafka and marks them sent. Because the
business row and the outbox row commit atomically, you can never
have one without the other. Delivery is at-least-once, so
consumers stay idempotent.
Say this: "Never dual-write. Write the event to an outbox table in the same transaction, then let CDC publish it - atomicity by construction, with idempotent consumers."
Linearizability is not serializability
They're different guarantees on different things, and conflating them is the fastest way to sound junior. Linearizability is about single objects and real-time recency: the system behaves as if there's one copy, and once a write completes every later read sees it. Serializability is about transactions: the result equals some serial order of whole transactions, with no claim about real time. A system can be one without the other: Serializable Snapshot Isolation is serializable but not linearizable (it reads from a slightly stale snapshot); a single linearizable register is linearizable but has no transactions at all. The combined guarantee - serializable and linearizable - is called strict serializability (what Spanner targets).
Say this: "Linearizability = single-object recency; serializability = transaction ordering. Both together is strict serializability."
Unbundling the database - and when NOT to
The modern data stack is the unbundled database: an event log (Kafka) as source of truth, CDC feeding it, stream processors (Flink) maintaining derived views, and a lakehouse plus feature/vector stores as rebuildable materialized views. The senior addition is knowing the cost: unbundling means you now own consistency, ordering, and reprocessing across systems that a single database gave you for free. So don't unbundle a problem that fits comfortably in one strongly-consistent database - you'd take on distributed-systems complexity for no benefit. Unbundle when scale, independent read patterns, or team boundaries actually demand it.
Say this: "The modern stack is the unbundled database with the log as its spine - but I only unbundle when one database can't meet the scale or access patterns, because unbundling moves the correctness burden onto me."
Weapons, with code
VI-1 · The transactional outbox, in SQL
The single most-asked "how do you avoid dual writes" pattern. The business change and the event insert share one transaction; CDC does the rest.
-- one atomic transaction: business row + event row commit together BEGIN; UPDATE orders SET status = 'PAID' WHERE id = :order_id; INSERT INTO outbox (id, aggregate_id, type, payload, created_at) VALUES (:uuid, :order_id, 'OrderPaid', :json_payload, now()); COMMIT; -- Debezium tails the DB log and publishes new outbox rows to Kafka, -- then marks them sent. Delivery is at-least-once, so consumers dedupe.
VI-2 · Slowly Changing Dimensions (SCD) - the history cheat-sheet
How a warehouse remembers the past when an attribute changes. Interviewers ask this by name; Type 2 is the one you must know cold.
| Type | What it does | Use when |
|---|---|---|
| Type 0 | Never changes (retain original) | Immutable facts (date of birth, original signup date) |
| Type 1 | Overwrite; no history kept | Corrections where the old value is worthless |
| Type 2 |
New row per change, with valid_from/valid_to
+ is_current flag
|
The default for real history - "what was the value at time T?" |
| Type 3 | Keep a "previous value" column only | You only ever need the immediately prior value |
| Type 4 | Current row in main table, full history in a separate table | History is large and queried differently from current |
| Type 6 | Combine 1+2+3 (current flag + dates + prior column) | You need both point-in-time and fast current lookups |
Modern note: the lakehouse gives you some of this for free - Iceberg / Delta time travel lets you query a table "as of" a snapshot or timestamp, so slowly-changing history and audit become a table-format feature, not just a modeling chore.
VI-3 · Idempotency keys - three implementations
The universal answer to "how do you make retries safe." Pick by where the key comes from.
-
Deterministic content hash - key =
sha256(natural_business_fields). Two identical logical events hash the same, so a duplicate is detected even without a client-supplied id. Good for reprocessing and backfills. -
Client / producer-supplied key - the caller sends
an
Idempotency-Keyheader (how payment APIs do it); the server records it and returns the stored result on a repeat. Good for user-facing "charge once" APIs. - Sink-side dedup with TTL - the consumer keeps a set of recently-seen keys (in RocksDB state or Redis) for a time window and drops duplicates. Good for at-least-once streams where you only need dedup within a bounded lateness window.
VI-4 · The data-quality & lineage stack
OpenAI-style "design a data-quality / drift framework" wants this vocabulary, in layers:
- Validation / tests (assert expectations at pipeline boundaries) - Great Expectations, Soda, dbt tests: row counts, null rates, uniqueness, ranges, referential checks.
- Observability / drift (detect the anomaly you didn't write a test for) - Monte Carlo, Bigeye, Metaplane: freshness, volume, and distribution monitoring with alerting.
- Lineage / catalog (know what feeds what) - DataHub, OpenLineage, Marquez: column-level lineage for impact analysis and audits.
- The failure policy - a tiered response: for a minor issue, alert and continue; for a moderate one, fall back to the last-good data; for a severe one, halt the pipeline before bad data reaches consumers. Naming this tiering is the senior move.
VI-5 · Partitioning strategies that avoid hot spots
- Compound key (hash a partition key, sort within it) - even distribution and range scans inside a partition. The Cassandra default move.
- Time-bucketed + salt - for time-series, bucket by day/hour and add a small salt so the newest bucket doesn't become a write hot spot.
- Iceberg hidden partitioning + partition evolution - partition on a derived value (e.g., day of a timestamp) without exposing it in queries, and change the scheme later without rewriting the table.
- Tenant isolation - give the heaviest tenants dedicated partitions/shards so one whale can't starve everyone else.
BACKWARD (default): new schema reads old data →
upgrade consumers first. FORWARD:
old schema reads new data → upgrade
producers first. FULL: both, so
add/remove only optional fields with defaults → either order. If
you remember one line:
backward = consumers first, forward = producers first.
The STAR story framework
The winning behavioral answers are crisp stories that prove you've done the hard systems work under real constraints. Here's the template and the concept areas worth preparing - build each story from your own experience.
- Quantify the Result. "Cut duplicate events to zero," "dropped p99 from 900 ms to 120 ms," "migrated 40 services with no downtime." Numbers are what interviewers remember.
- Keep the Action to three or four concrete decisions - the specific technical moves and why, not a play-by-play.
- End with the transferable principle - one sentence that generalizes what you did ("I scope exactly-once to the money path and use idempotent upserts elsewhere").
- Pick the weakest true story that still shows judgment. You don't need a heroic outage; a well-reasoned design decision is often stronger.
- One story can cover several concepts - a single exactly-once pipeline touches idempotency, the outbox, CDC, and reconciliation.
Situation - the system and the constraint (scale, latency SLO, the cost of being wrong). Task - the specific guarantee or outcome you owned. Action - three or four concrete technical decisions and the tradeoffs behind them. Result - the quantified outcome, plus the principle you now carry.
Eight concepts worth a prepared story
For each: what a strong story proves, and example angles across domains - find the one that matches your experience.
Schema evolution under load
Proves: you can change data contracts without downtime and coordinate the rollout across teams.
Example angles: adding a field to an event schema consumed by many services via a registry FULL-compatibility staged rollout; versioning an ML dataset or eval schema for reproducibility; evolving a FIX / market-data message so running strategies keep working.
The hot partition / skew
Proves: you fix uneven load by modeling, not just by adding capacity.
Example angles: a few dominant keys or tenants overloading one shard (salting + scatter-gather); write-sharding a time-ordered key; killing a skewed Spark task that was the long pole of a job.
Exactly-once & the dual-write fix
Proves: you can guarantee no double-counting with a full audit trail.
Example angles: idempotency keys + a transactional outbox + CDC + an idempotent sink on a billing / payments / ordering stream; de-duplicating an at-least-once pipeline; reconciling source vs derived counts to zero.
Reconciliation / recompute correctness
Proves: you make derived data provably correct, not just probably correct.
Example angles: a periodic full recompute from immutable events; catching a drift bug before it reached reporting; a backfill that safely corrected years of historical data.
Lineage & governance of sensitive data
Proves: you can answer "where did this come from / who can see it" and pass an audit.
Example angles: classifying PII in schemas and enforcing retention / masking; instrumenting lineage in a catalog; training-data provenance for compliance and reproducibility.
Choosing the isolation level
Proves: you pick consistency deliberately, by the cost of being wrong.
Example angles: SERIALIZABLE or row locks on a check-then-decide invariant (balance, inventory, seat); handling serialization-failure retries with backoff; avoiding a cross-shard 2PC via aggregate boundaries or a saga.
The latency incident
Proves: you debug by measurement and fix tail latency at its root.
Example angles: a p95 / p99 regression traced through fan-out with distributed tracing to a hot partition, GC pause, slow downstream, or cache miss; adding a guardrail so it cannot silently recur.
A data-quality / drift-detection framework
Proves: you make data trustworthy by default, with early alerting.
Example angles: validation gates (volume, nulls, keys, ranges) plus distribution / drift monitoring plus a tiered failure policy (fallback vs halt); cutting the rate of bad-data incidents that reach consumers.
Prepare one story per concept from your own work, lead with the quantified result, and close with the transferable principle. Behavioral rounds are buying judgment under real constraints - give them evidence, not adjectives.
Architectures worth citing
Concrete architectures make abstract answers credible. Use these as illustrations - and, where noted, as a "I've read about how you work" signal. Keep numbers directional.
Stripe - payments data, done correctly
- No-ETL ingestion: a managed Data Pipeline replicates data straight into warehouses (Snowflake / Redshift), avoiding brittle custom export jobs.
- Correctness culture: tiered data-quality gates and a "recompute from source of truth" reconciliation habit - derived numbers must be provably correct, not approximately right.
- Lineage: catalog/lineage tooling so every financial figure can be traced to its inputs for audit.
Use it: cite Stripe's no-ETL replication, catalog-based lineage, and full-recompute reconciliation when an interviewer asks how you'd guarantee financial-grade data quality.
Uber - the feature store and exactly-once
- Michelangelo / Palette feature store: the same feature definitions serve offline training and online serving, eliminating training/serving skew (offline in Hive, online in Cassandra/Redis).
- Exactly-once event processing: Flink + Kafka with idempotent upserts into Apache Pinot for real-time analytics - the canonical end-to-end EOS reference.
Use it: the go-to reference for both the feature-store prompt and the exactly-once prompt.
Netflix - log + derived views at scale
- Keystone pipeline: a massive Kafka + Flink backbone routing trillions of events into many derived sinks.
- Data mesh + Iceberg: teams own derived data products; Iceberg tables give rebuildable, time-travelable history - the unbundled database in production.
Use it: the canonical example of "log + derived materialized views," and why Iceberg is the table format of choice for reprocessable history.
Anthropic & OpenAI - data at frontier scale
- Anthropic: interpretability and training workloads involve very large shuffles and multi-cloud data movement; the cultural emphasis is research-to-production hardening - making experimental pipelines trustworthy and reproducible.
- OpenAI: operates enormous numbers of datasets and pipelines; internal data-agent tooling accelerates exploration, and "judgment when using AI tools" is explicitly evaluated - you own validating what the tool produces.
Use it (Anthropic): show you understand their data-at-scale challenges; pair it with a thoughtful question about their research-to-production hardening philosophy. Use it (OpenAI): tie your answer to their explicit "judgment with AI tools" bar - "I'd use an agent to accelerate exploration, but I own validating its output."
Scale figures for private companies move fast and are often reported loosely. Cite architecture shapes ("feature store to kill training/serving skew," "Flink + Kafka for exactly-once," "log plus rebuildable Iceberg views") with confidence, and treat specific petabyte/QPS numbers as directional. A wrong precise number is worse than a right general one.
The seven-week plan
This is a focused seven-week runway you can start whenever your job search begins. Three phases - consolidate, target, interview - each with a concrete deliverable. Adjust to your real interview dates.
| Week | Focus | Deliverable |
|---|---|---|
| 1 | Foundations - Ch 1-4. Nail percentiles, tail amplification, LSM vs B-tree, schema evolution. | Re-answer every Ch 1-4 Interview box aloud from memory; write your own one-line answers. |
| 2 | Distributed core - Ch 5-7. Replication modes, partitioning + hot keys, isolation levels + write skew. | Whiteboard a partitioned, replicated store and explain its consistency; recite the isolation ladder. |
| 3 | Hard theory + weapons - Ch 8-9, Part V, Part VI. The contested six until fluent. | Deliver all six "Say this" lines cold; write the outbox SQL and the SCD table from memory. |
| 4 | Derived data + design drills - Ch 10-12. Run 10 design prompts from Part 0 to a 40-min clock. | Two full design answers recorded and self-reviewed against the "define before you build" rule. |
| 5 | Target A (AI labs). RLHF/eval data, versioning & lineage, drift detection, research-to-prod hardening. | Design "RLHF dataset versioning + lineage" and "data-quality/drift framework" end to end. |
| 6 | Target B (quant). Time-series storage, market-data normalization, low-latency ingestion; be honest about language depth. | Design "normalize N exchange feeds" with exactly-once and a time-series layout; pick your language lane. |
| 7+ | Interview & recover. Back-to-back mocks; drill the "why this mission" story; convert misses into fixes. | Mocks with a peer; a tight "why this company" answer; a written post-mortem after each real loop. |
- You can recite the three consistency definitions (linearizability, serializability, causal) distinctly, without hesitating.
- You can whiteboard an end-to-end exactly-once pipeline (idempotent producer → transactional read-process-write → checkpointed state → idempotent sink) with Flink + Kafka + Iceberg in about 30 minutes.
- You open every design prompt with requirements + a stated correctness invariant before drawing a single box.
- You have a quantified STAR story ready for each of the eight concepts in Part VII.
- If your recruiter-screen → onsite conversion is below ~50%, the gap is usually the "why this mission" framing, not technical depth - re-record that answer until it's specific and genuine.
Consolidate the book (Weeks 1-3), target each company's real loop (Weeks 4-6), then interview with mocks and recovery (Week 7+). The plan converts knowledge into offers - the difference between people who know DDIA and people who get hired for it.
Rapid-fire glossary
The night-before sheet. If you can give the one-line answer to each of these, you can hold a senior conversation about data systems. Cover the right column and quiz yourself.
| Term | One-line answer |
|---|---|
| Fault vs failure | A component deviating from spec vs the whole system stopping service; reliability keeps the first from causing the second. |
| Tail-latency amplification | When a request waits on many backends, the slowest one sets the user's latency, so backend p99 ≈ user p50. |
| Percentile (p99) | The value below which 99% of response times fall; it exposes the unlucky requests the average hides. |
| Schema-on-read / -write | Interpret structure when reading (flexible) vs enforce it when writing (guaranteed). |
| LSM-tree | Buffer writes in memory, flush sorted files, compact in background; great write throughput, higher read/space amplification. |
| B-tree | Sorted fixed pages updated in place; predictable low-latency reads, higher write amplification. |
| Bloom filter | A probabilistic set membership test that lets an LSM skip files that can't contain a key (no false negatives). |
| Write/read/space amplification | One logical write causing many physical writes; one read touching many files; data using extra space - you trade these off. |
| OLTP vs OLAP | Many small low-latency transactions vs large analytical scans; rows suit the first, columns the second. |
| Columnar storage | Store each column contiguously so analytics reads only needed columns and compresses well. |
| Avro vs Protobuf | Avro resolves reader/writer schemas at read time (great for files); Protobuf uses numbered tags (great for RPC). |
| Backward / forward compat | New code reads old data / old code reads new data; you need both during rolling deploys. |
| Replication (leader/leaderless) | Copies of data; one writer streams a log to followers, or any replica takes writes with quorums. |
| W + R > N | Quorum condition ensuring read and write sets overlap so a read sees the latest write. |
| Read-your-writes | A guarantee that you always see your own prior updates despite replication lag. |
| Range vs hash partitioning | Contiguous ranges (good scans, hot-spot risk) vs hashed keys (even spread, no efficient range scans). |
| Hot key / salting | One key overloading a partition; add a suffix to split it across partitions and gather on read. |
| Consistent hashing (caveat) | Minimizes data movement on rebalance, but has load-balance weaknesses; vnodes/newer schemes improve it. |
| ACID | Atomicity, Consistency, Isolation, Durability - the transaction guarantees; Isolation does the hard work. |
| Read committed | No dirty reads or writes; still allows non-repeatable reads, lost updates, write skew. |
| Snapshot isolation / MVCC | Reads see a consistent snapshot via multiple versions; prevents non-repeatable reads, not write skew. |
| Write skew | Two transactions read-decide-write on overlapping data and jointly break an invariant; needs serializable or locks. |
| Serializable / SSI | Result equals some serial order; SSI achieves it optimistically by aborting conflicting transactions. |
| Fencing token | A monotonically increasing number a resource checks to reject a stale (paused-then-woken) lock holder. |
| TrueTime | Spanner's bounded-uncertainty clock (GPS + atomic) that waits out the interval for globally-consistent timestamps. |
| Linearizability | Single-object real-time recency: behaves like one copy; once a write completes, all later reads see it. |
| Serializability | Transaction-level guarantee that the outcome equals some serial order; no real-time claim. |
| Causal consistency | Preserves cause-before-effect ordering; the strongest model that stays available under partition. |
| CAP vs PACELC | Under partition, choose C or A; else, trade latency vs consistency - PACELC covers the everyday case CAP ignores. |
| Consensus / Raft | Getting nodes to agree despite failures; Raft elects a leader and replicates an ordered log to a quorum. |
| Total order broadcast | Every node sees the same operations in the same order; the backbone of strongly-consistent systems. |
| MapReduce philosophy | Immutable inputs + deterministic functions + retryable tasks = near-free fault tolerance and safe reprocessing. |
| Spark DAG | Model a job as an in-memory graph of transformations, recomputing lost partitions from lineage. |
| Event vs processing time | When an event happened vs when it's handled; correct time aggregations use event time. |
| Watermark | A moving assertion that all events up to time T have likely arrived, so a window can close. |
| Windowing | Bucketing stream events by time: tumbling, hopping/sliding, or session windows. |
| At-least-once / exactly-once | Never lose but maybe duplicate vs each event's effect applied once (engineered end to end). |
| CDC | Change Data Capture: turn a database's change log into an event stream (Debezium) to sync derived stores. |
| Dual-write problem | Updating a DB and a queue as two operations can leave them inconsistent on a crash between them. |
| Outbox pattern | Write the event to an outbox table in the same transaction as the business change; CDC publishes it. |
| SCD Type 2 | Keep history by adding a new row per change with valid_from/valid_to and an is_current flag. |
| Feature store / skew | Serve identical features to training and serving from one definition to eliminate training/serving skew. |
| Lakehouse / Iceberg | Columnar files on object storage with ACID table semantics and time travel (Iceberg / Delta / Hudi). |
| Vector DB / HNSW | Store embeddings and query nearest-neighbors for semantic search/RAG; HNSW is the common index. |
| Unbundling the database | Event log as source of truth; every store is a rebuildable materialized view derived from it. |
A data system is an event log as the source of truth with rebuildable derived views around it; you keep it reliable (faults ≠ failures), scalable (percentiles, partitioning, replication), and correct (the right consistency and isolation, idempotency, exactly-once done end to end). Say that clearly, back it with a quantified story, and you interview like someone who has built these systems.