all_lessons / data_intensive_systems / 27 · production atlas lesson 28 / 35 · ~19 min

Part 12 · Applied & interviews

Production Systems Atlas: Where Real Databases Sit in the Design Space

The signpost lesson (26) closed Part 11 by mapping the DDIA 2nd edition's expanded scope back onto the mechanisms this track built. This lesson opens the applied part by turning those mechanisms into a coordinate system, not a catalog. We use the axes a data system varies along: the storage engine (append log, B-tree, LSM, columnar — lessons 03/04), the replication model (single-leader, multi-leader, leaderless quorum — 08/09), the partitioning scheme (hash vs range — 11), the transaction and isolation story (13), the consistency model (eventual through linearizable — 16), and whether the system is built around an ordered durable log (19). Every real database is a point in that space: a specific bundle of choices, optimized for one access pattern and paying for it everywhere else. This lesson places the systems you will actually be asked about — PostgreSQL, MySQL, Cassandra, DynamoDB, Riak, Bigtable, HBase, Kafka, Pulsar, Kinesis, Redis, Elasticsearch, OpenSearch, Spark, Flink — at their coordinates, says what each optimizes and what it gives up, and ends with the rule that turns the atlas into a decision: choose by the access pattern plus the invariant you must hold.

Source note
This lesson is an original synthesis. It does not draw from a single DDIA chapter; instead it locates real systems against the design-space axes the whole track has already taught, cross-checked against each project's public documentation and architecture papers (Dynamo, Bigtable, the Kafka and Lucene docs, the Spark/Flink guides). Every mechanism here was built earlier in the track — this lesson only assigns coordinates. Where a system's behavior is configurable, the table states the common default and flags the knob.
Linear position
Prerequisite: the mechanism arc, Parts 1–11. You can already reason about storage engines (03/04), single- and multi-leader and quorum replication (08/09), hash vs range partitioning and hot keys (11), ACID isolation levels (13), consistency models from eventual to linearizable (16), consensus and the coordination service (17), and the ordered log behind CDC and streams (19/20). We re-derive none of it — we cite the home lesson for each axis.
New capability: Given a real system named in an interview, state its position on every axis from memory, name the one thing it optimizes and the one thing it gives up, and reach for the right family when a workload is described rather than a product.
The plan
Six moves. (1) Fix the axes as a checklist so every system gets scored the same way, and show the master comparison table up front as the map. (2) Relational OLTP — PostgreSQL vs MySQL. (3) The Dynamo lineage — Cassandra, DynamoDB, Riak. (4) The Bigtable lineage — Bigtable, HBase. (5) Logs and streams — Kafka vs Pulsar, with Kinesis; in-memory Redis; search Elasticsearch / OpenSearch; compute Spark vs Flink. (6) The decision rule: pick by access pattern plus the invariant, with the failure mode of picking by brand. No widget — a table teaches placement better than a knob.

1 · The axes, and the master map

To place a system you score it on the same six axes every time. These are exactly the axes the track built, so each is a link back to where it was derived, not a new idea:

Storage engine
Read-optimized B-tree / heap (update in place, fast point reads — L03) vs write-optimized LSM-tree (append + compact, high write throughput — L04) vs columnar (scan-optimized analytics — L04).
Replication
Single-leader (one writer, an order for free — L08) vs leaderless quorum (w + r > n, no failover — L09) vs primary–replica per shard.
Partitioning
Hash (even spread, no range scans) vs range (ordered, scannable, risk of hot ranges) — L11.
Transactions / isolation
Multi-object ACID with serializable/SSI down to single-row atomicity only — L13.
Consistency
Linearizable (recency, one copy illusion — L16) vs tunable vs eventual.
Log-centric?
Is the system itself an ordered durable log (the substrate for CDC and streams — L19), or does it merely keep a WAL internally?

Scored that way, the seven families fall out cleanly. Here is the whole map; the rest of the lesson defends each row.

System familyStorage engineReplicationPartitioningConsistency / txnBest-fit workload
PostgreSQL / MySQL
relational OLTP
B-tree + heap (L03)Single-leader (L08)Single node by default; range/hash via extensionsMulti-object ACID; serializable/SSI available (L13)Transactional system of record; joins; strong invariants
Cassandra / DynamoDB / Riak
Dynamo lineage
LSM-tree (L04)Leaderless quorum (L09)Hash (consistent hashing) (L11)Tunable; single-row atomicity; eventual by default (L16)Huge write volume, predictable key lookups, multi-region availability
Bigtable / HBase
Bigtable lineage
LSM SSTable (L04)Single master + tablet/region servers (L17)Range over row key (L11)Single-row atomicity; strong within a rowWide-column, ordered scans over a sorted key space; time-series
Kafka / Pulsar / Kinesis
log / streaming
Append-only segment log (L03, L19)Leader per partition + ISR followers (L08)Hash/key over partitions (L11)Per-partition total order; durable, replayableEvent backbone, CDC transport, stream sources (L20)
Redis
in-memory
RAM + AOF/RDB durability (WAL-like — L03)Single-leader async (L08)Hash slots (16384) in Cluster (L11)Single-key atomic ops; per-key, not multi-key linearizableCache, rate limiter, leaderboard, low-latency point lookups
Elasticsearch / OpenSearch
search
Inverted index, Lucene segments (LSM-like — L04)Primary–replica shards (L08)Hash doc-id → shard (L11)Near-real-time, eventually visible; not a system of recordFull-text + analytics search over a copy of your data
Spark / Flink
compute (not storage)
None of its own; reads external storesn/a (checkpoints for recovery)Shuffle / keyBy partitions (L18)Exactly-once via checkpoint + idempotent sink (L19)Spark: batch + micro-batch; Flink: low-latency true streaming

Read the table as a map of trade-offs, not a ranking. Moving down the rows you trade away multi-object transactions and joins for write throughput and availability; moving from hash to range partitioning you trade even key spread for ordered scans; the last two rows are not stores at all — search is a derived index over a real system of record, and Spark/Flink are compute that reads and writes the others. The next sections give each family its coordinates and the one-sentence "optimizes / gives up".

2 · Relational OLTP: PostgreSQL vs MySQL

The relational family is the default system of record: a B-tree storage engine (lesson 03) for fast point reads and in-place updates, a heap or clustered layout for rows, single-leader replication (lesson 08) so every write passes through one node and inherits a total order for free, and — the feature that defines the family — multi-object ACID transactions with real isolation levels (lesson 13), up to serializable. This is the only family in the atlas where you can hold an invariant that spans several rows ("debit one account, credit another, and never let the sum drift") inside one atomic, isolated transaction.

PostgreSQLMySQL (InnoDB)
Row storageHeap + separate indexes; MVCC keeps old row versions, needs VACUUMClustered index on the primary key (the table is a B-tree by PK); secondary indexes point at the PK
Default isolationRead Committed; Serializable Snapshot Isolation (SSI) available — true serializability that detects conflicts rather than lockingRepeatable Read by default (with next-key locks); Serializable available via locking
ConcurrencyMVCC throughout; SSI is optimistic (aborts on dangerous read/write skew)MVCC + gap/next-key locking; more pessimistic
ReplicationSingle-leader, streaming WAL; logical decoding exposes the WAL as a change stream (the clean CDC source — L19)Single-leader binlog (statement/row/mixed); the binlog is the standard CDC source

Optimizes: correctness over rich relational data — joins, multi-row invariants, and the strongest isolation in the atlas. Gives up: horizontal write scale. One leader is a write bottleneck and a single point of failure; scaling writes means application-level sharding or a distributed-SQL layer (Spanner/CockroachDB/Vitess), which buys scale by adding consensus (lesson 17) and giving up some of the single-node simplicity. The practical split between the two: PostgreSQL leans toward feature richness and true serializability via SSI; MySQL/InnoDB leans toward a clustered-index layout that makes PK lookups and PK-ordered scans very fast. Both are the right default when the data has invariants you cannot afford to break — and both expose their replication log as the CDC source the rest of the track's derived-data pipelines (lesson 20) feed from.

3 · The Dynamo lineage: Cassandra, DynamoDB, Riak

Amazon's 2007 Dynamo paper defined this family, and Cassandra, DynamoDB, and Riak are its descendants. The coordinates are the opposite corner of the space from relational: an LSM-tree engine (lesson 04) that turns every write into a cheap sequential append plus background compaction, so write throughput is enormous; leaderless quorum replication (lesson 09) where the client writes to w of n replicas and reads from r, sized by the condition w + r > n when you need fresh reads; hash partitioning via consistent hashing (lesson 11) for even spread and cheap rebalancing; and only single-row (single-item) atomicity — no multi-row transactions in the classic design.

Cassandra
Open-source, self-hosted leaderless. Tunable consistency per query (ONE, QUORUM, ALL). Wide-column data model; you design tables around the read query, not normalization. Conflict resolution is last-write-wins by timestamp — convenient, lossy (L09).
DynamoDB
AWS-managed. Eventually-consistent reads by default, strongly consistent reads as a per-request flag (a quorum read). Single-item ops are atomic; a later TransactWriteItems adds limited multi-item transactions on top. Partition key + sort key = hash then range within a partition.
Riak
The closest to the paper: leaderless, n/w/r exposed directly, and the honest conflict story — version vectors detect concurrency and return siblings the application merges, rather than silently dropping a write (L09).

Optimizes: write throughput, predictable point/range-within-partition lookups, and multi-region availability with no failover step (there is no leader to fail over — lesson 09). Gives up: joins, multi-row transactions, and ad-hoc queries — you can only query efficiently along the partition and clustering keys you chose up front, and choosing them wrong means a full scan or a redesign. The default eventual consistency also means a read can miss a just-acked write unless you pay for a quorum read. The classic interview tell: if the prompt says "10× write volume, key-based lookups, must survive a region outage," this family fits; if it says "report joining three entities with a balance that must always reconcile," it does not.

4 · The Bigtable lineage: Bigtable, HBase

Google's Bigtable (and its open-source clone HBase on HDFS) shares the LSM engine with Dynamo but flips two axes. Storage is the same LSM SSTable design (lesson 04) — sorted string tables flushed from an in-memory memtable and merged by compaction — but partitioning is range, not hash (lesson 11): rows are kept sorted by row key and split into contiguous tablets (Bigtable) or regions (HBase). And replication is not leaderless: a single master (coordinated via Chubby/ZooKeeper, lesson 17) assigns tablets to servers, giving strong, single-row consistency rather than tunable quorums.

range-partitioned key space (Bigtable tablets / HBase regions) row keys, sorted: [ user#0001 … user#0999 ] [ user#1000 … user#4242 ] [ user#4243 … ] └── tablet A (server 1) ──┘ └── tablet B (server 2) ──┘ └ tablet C ┘ │ sorted ⇒ a range scan "all keys user#1000..user#1099" reads ONE contiguous tablet — cheap. │ hot tail problem: append-only keys like ts#20260617T08:00, ts#20260617T08:01, … all land at the END of the last tablet ⇒ one server takes every write (fix: salt / hash-prefix the key — trades the clean scan for spread)

Optimizes: ordered scans over an enormous sorted key space — "give me every event for this user between these two timestamps" reads one contiguous tablet, which hash partitioning cannot do. Strong per-row consistency, wide sparse columns, and time-series layouts all fall out of the sorted-range design. Gives up: even load when keys are sequential (monotonic keys create the hot-tail problem the diagram shows — the standard fix is to salt or hash-prefix the key, trading the clean scan for spread), and anything beyond single-row atomicity. No joins, no multi-row transactions. This is the family for wide-column, scan-heavy, time-ordered data where the access pattern is a range over a sorted key.

5 · Logs, memory, search, and compute

Kafka vs Pulsar (and Kinesis): the partitioned durable log

A log system is not a database you query by key — it is the ordered durable log itself (lessons 03, 19) exposed as a product: an append-only sequence, partitioned for throughput (lesson 11), replicated, and replayable. Within one partition there is a total order (a monotonic offset); across partitions there is none, which is why the partition key is the ordering contract. This is the event backbone and CDC transport the whole derived-data story (lesson 20) runs on.

KafkaPulsarKinesis
ReplicationLeader per partition + in-sync replica (ISR) followers; a write is durable once the ISR set acks (a quorum-like rule — L08)Broker layer is stateless; storage is BookKeeper bookies that quorum-replicate ledger segmentsManaged; shards replicated across AZs by AWS
Compute / storage splitCoupled — a broker owns both serving and its partition's data on local diskDecoupled — brokers are stateless, segments live in BookKeeper, so you scale serving and storage independently and rebalance cheaplyFully managed; you size shards, AWS owns the rest
Ordering / retentionPer-partition offset order; retain by time or size, replay from any offsetPer-partition order; tiered storage offloads old segments to object storagePer-shard order; 24 h to 365 d retention

Optimizes: durable, ordered, replayable event transport at high throughput — the substrate for CDC, stream processing, and event sourcing. Gives up: random-access reads and queries by content — you consume sequentially by offset, not "find the record where x = y." Pulsar's broker/segment split is its main differentiator from Kafka: stateless brokers make scaling and rebalancing cheaper at the cost of an extra moving part (BookKeeper). Kinesis is the managed AWS analog — same partitioned-log idea, less to operate, AWS-shaped limits.

Redis: in-memory data structures

Redis keeps its dataset in RAM, which is why a point lookup is sub-millisecond and why it is the default cache, rate limiter, and leaderboard. It is not just a key-value cache — it serves data structures (strings, hashes, sorted sets, streams) with atomic operations on a single thread, so each command is atomic without locks. Durability is optional and WAL-shaped (lesson 03): AOF (append-only file — every write command logged, like a WAL) and/or RDB (periodic point-in-time snapshots); you trade fsync frequency against durability. Replication is single-leader async (lesson 08), so a primary can ack a write and die before the replica sees it — Redis is not a linearizable system of record. Redis Cluster partitions by 16384 hash slots (lesson 11) mapped to nodes. Optimizes: latency and atomic per-key operations. Gives up: durability guarantees and dataset size bounded by RAM and budget; treat it as a fast derived layer in front of a real store, not the source of truth.

Elasticsearch / OpenSearch: the inverted index

Search engines (built on Apache Lucene) store an inverted index — term → list of documents containing it — in immutable segments that are written once and merged, behaving like an LSM-tree (lesson 04): writes append new segments, background merges compact them, and a document is only near-real-time visible (after a refresh, typically ~1 s), not instantly. Data is sharded by hashing the document id, each shard is a primary with replicas (lessons 08/11). The load-bearing point for an interview: Elasticsearch is not a system of record. It is a derived full-text/analytics index over a copy of data whose source of truth lives in Postgres/MySQL/Kafka — populated by CDC (lesson 20), and rebuildable from that source if a shard is lost. Optimizes: full-text relevance ranking and aggregations over large document sets. Gives up: being authoritative — no real transactions, eventual visibility, and a refresh/merge cost on every write.

Spark vs Flink: compute, not storage

The last family stores nothing of its own; Spark and Flink are processing engines that read from and write to the systems above. Both partition work by key (the shuffle/keyBy — lesson 18) and both achieve exactly-once effects via checkpointing plus idempotent sinks (lesson 19). The difference is the execution model:

SparkFlink
ModelBatch first; streaming is micro-batch (Structured Streaming) — small batches every few hundred msTrue streaming — one record at a time through a standing dataflow
LatencySub-second to seconds (batch boundary cost)Millisecond-scale per-event latency
State / recoveryLineage of RDDs/dataframes; recompute lost partitionsLarge managed keyed state + periodic checkpoints (Chandy–Lamport); restore and replay from the source offset
Best fitLarge batch ETL, training-set builds, and "good enough" streaming on one engineLow-latency event processing, complex event-time windows, long-lived stateful streams

Optimizes (Spark): a single engine for heavy batch and tolerable-latency streaming, with a huge ecosystem. Optimizes (Flink): genuine low-latency, event-time-correct streaming with first-class large state. Both give up: being a store — they need a durable source (Kafka, a lake) and a real sink, and their correctness guarantees only hold end-to-end if the sink is idempotent (lesson 20).

6 · The rule: choose by access pattern + the invariant

The atlas exists to be collapsed into one decision. Do not start from a product ("we use Postgres" / "let's put it in Mongo"). Start from two questions, in this order:

1What is the access pattern? Point lookup by key? Range scan over a sorted key? Full-text search? Sequential replay of events? Multi-row join? Big analytical scan? This selects the storage engine and partitioning — B-tree for point/relational, LSM+hash for write-heavy key lookups, LSM+range for ordered scans, inverted index for text, append-log for replay, columnar for analytics.
2What invariant must you hold? A multi-row constraint that can never be violated (a balance, a uniqueness rule)? Then you need multi-object ACID — relational, single-leader (08/13/16). Only single-row atomicity needed, and stale reads are tolerable? Then the Dynamo/Bigtable families open up and you gain scale and availability. The invariant selects the replication model and consistency level.

Access pattern picks the engine; the invariant picks the consistency. A "user profile read by id, edited rarely, must never lose a concurrent edit" is a point lookup (LSM/hash) with a no-data-loss invariant (version vectors + siblings, lesson 09) — Riak/DynamoDB territory. A "ledger with a balance that must reconcile" is relational and single-leader, full stop, regardless of volume — and if volume forces sharding, you reach for distributed SQL that keeps the invariant via consensus (lesson 17), not for a Dynamo store that abandons it. A "search box over products" is an inverted index derived from the relational source of truth via CDC (lesson 20) — never the source itself.

Mis-placements (interview red flags)

  • Elasticsearch as system of record. Eventual visibility + no transactions; a lost shard means lost data unless you can rebuild it from a real source. It is a derived index.
  • Redis as durable store. Async single-leader + optional fsync means acked writes can vanish on failover. It is a cache, not a source of truth.
  • Dynamo-family for multi-row invariants. Single-item atomicity cannot enforce "the sum stays zero" across items; you would be hand-rolling transactions on a store designed to avoid them.
  • Monotonic key into a range-partitioned store. Timestamps or sequence ids as the row key send every write to one tablet (the hot-tail problem); salt the key.
  • Picking by brand familiarity. "We know Postgres so we'll shard it to 50 nodes" ignores that the access pattern (key lookups, huge write volume) wanted an LSM/quorum store.

Placement checklist

  • State the dominant access pattern in one phrase before naming any product.
  • Name the hardest invariant and whether it spans more than one row/item.
  • If multi-row invariant ⇒ relational + single-leader (08/13); else the LSM families open up.
  • Point/key ⇒ hash; ordered scan ⇒ range; text ⇒ inverted index; replay ⇒ log; analytics ⇒ columnar.
  • Need fresh reads on a quorum store? Verify w + r > n (09).
  • Is the candidate store the source of truth, or a derived view that must be rebuildable from one (20)?
  • Will the write key stay evenly distributed, or is it monotonic (hot-tail / hot-key — 11)?

Checkpoint exercise

Try it
You are handed four workloads. For each, name the family from the master table and justify it on both axes (access pattern + invariant), then name one system that would be a red-flag wrong choice and why. (a) A payments ledger: every transfer debits one account and credits another, the total must always reconcile, ~5,000 writes/s. (b) An IoT fleet writing one sensor reading per device per second, queried as "all readings for device D in the last hour", ~2,000,000 writes/s. (c) A product catalog search box with typo tolerance and faceted filters, source data already in Postgres. (d) A shopping-cart store that must stay writable across two regions and must never lose an item a user added. (Reference answers: a → relational single-leader, B-tree + multi-object ACID axes (03/13); b → Bigtable/HBase range over (device, ts); c → Elasticsearch derived via CDC, not source of truth; d → Dynamo-family leaderless with version vectors + sibling merge, never LWW.)

Where this points next

The atlas tells you where a system sits when it is healthy. But interviews and on-call both live in the moments when it is not: the leader that died before its last write replicated, the cache key that expired and stampeded the database, the CDC consumer that fell hours behind. Lesson 28 turns each axis of this atlas into a failure timeline — a time-ordered ASCII trace of what actually happens, who the actors are, and what the user sees — and pairs it with the quantitative drills (Little's Law latency budgets, partition sizing, compaction bandwidth, replication-lag and CDC-backlog arithmetic) that let you put real numbers on a design instead of hand-waving "it scales." The atlas is the map; lesson 28 is what happens when the map meets a fault.

Where is truth?
The atlas is really a map of where each system can hold the system of record. System of record: relational (PostgreSQL/MySQL) and the log (Kafka/Pulsar) are built to be truth; Bigtable/HBase and the Dynamo family can hold it for single-row-atomic data; Elasticsearch, Redis, and Spark/Flink never hold it. Copies / derived views: the search index, the cache, the materialized analytics table — all derived from one of the authoritative stores. Freshness budget: follower lag on relational, refresh interval (~1 s) on Elasticsearch, TTL on Redis, checkpoint interval on Flink. Owner: the team that owns the authoritative store; the derived stores inherit a rebuild contract. Deletion path: delete in the source, propagate via CDC to every derived index/cache. Reconciliation / repair: rebuild the derived store from the source's change log (re-index from CDC, re-warm the cache). Evidence it is correct: a derived store is trustworthy only if it is provably rebuildable from the source — if it cannot be rebuilt, it has silently become a second source of truth, which is the mis-placement red flag.
Takeaway
Every production data system is a point in the design space the track built — a specific bundle of storage engine (03/04), replication model (08/09), partitioning (11), transaction/isolation story (13), and consistency level (16), with some systems being the ordered log itself (19). Relational (PostgreSQL/MySQL) optimizes multi-object ACID correctness and gives up horizontal write scale; the Dynamo lineage (Cassandra/DynamoDB/Riak) optimizes write throughput and availability via LSM + leaderless quorum + hash partitioning and gives up joins and multi-row transactions; the Bigtable lineage (Bigtable/HBase) keeps the LSM engine but uses range partitioning for ordered scans at the cost of even load on monotonic keys; logs (Kafka/Pulsar/Kinesis) optimize durable replayable ordered transport and give up random access; Redis optimizes latency and atomic per-key ops with AOF/RDB durability and gives up being a system of record; Elasticsearch/OpenSearch optimize full-text relevance over a derived index and are explicitly not authoritative; Spark/Flink store nothing and optimize batch vs true-streaming compute respectively. The rule that turns the atlas into a decision: choose by the access pattern (which picks the engine and partitioning) plus the invariant you must hold (which picks the replication and consistency) — never by brand familiarity.

Interview prompts