Lesson 01 · The incumbent

Relational: tables, joins, and forty years of being right

The relational model (Codd, 1970) makes one radical bet: store facts, not objects. Every fact lives in exactly one place, as a row in a table; anything composite: an order with its customer and its items: is assembled at query time by joining facts back together. The payoff is that the database can guarantee things about your data that no application code has to enforce. To feel why that matters, first build the disease the model was designed to cure:

The anomaly machine

Update a customer's email: in two schema designs

Left: one denormalized table, customer data copied into every order (how a spreadsheet or a naive design does it). Right: normalized: orders reference the customer by id. Mia changes her email. Press the button on each design.

DESIGN A · one big table
orderitemcustomeremail
#101keyboardMiamia@old.fi
#102monitorMiamia@old.fi
#103usb hubMiamia@old.fi
#104cableRuirui@post.pt
DESIGN B · normalized: customers + orders
customers.idnameemail
c1Miamia@old.fi
c2Ruirui@post.pt
orders.iditemcustomer_id
#101keyboardc1
#102monitorc1
#103usb hubc1

Design A's failure has a name: an update anomaly: and two siblings: the insertion anomaly (can't record a new customer until they order something; there's no row to put them in) and the deletion anomaly (delete Mia's last order and Mia's email vanishes from the universe). Normalization is the discipline of decomposing tables until every fact is stored once, so anomalies become structurally impossible rather than carefully avoided. The famous mnemonic for third normal form: every non-key column depends on "the key, the whole key, and nothing but the key."

Joins: reassembly on demand

Normalization scatters the facts; JOIN gathers them. The query below doesn't care that the data lives in two tables: the relational engine matches rows on the fly:

SELECT o.id, o.item, c.email FROM orders o JOIN customers c ON o.customer_id = c.id WHERE c.name = 'Mia';

This is the model's superpower and its tax. Superpower: you can ask questions you didn't anticipate when designing the schema: any table joins to any table, and tomorrow's report needs no migration. Tax: joins cost CPU and, at scale, become the thing everyone tunes. (And when the data is spread across shards: Lesson 3 of the distributed systems course: cross-shard joins get so expensive that the models in the rest of this course start to look attractive. That's not a coincidence; it's the plot.)

Indexes: the other half of the engine

Find one row among a million

The users table has 1,000,000 rows. Find email = 'x9042@cyberdesk.io': first without an index, then through a B-tree.

rows examined
:
approx. page reads
:
verdict
:

The B-tree is a sorted, shallow tree of pages: root → branch → leaf, each hop narrowing the range, reaching any of a million keys in three or four page reads. Every relational database is, mechanically, two things: a B-tree library and a query planner deciding which B-trees to use. The catch you just didn't see: every index is a second copy of part of the table that must be updated on every write. Ten indexes = every INSERT does eleven writes. Read-heavy tables want many indexes; write-heavy tables want few: a dial you'll see the wide-column lesson turn all the way in one direction.

ACID: the adult in the room

The relational world's crown jewel is the transaction: Atomic (all or nothing: the WAL machinery from the replication course's mechanics lesson), Consistent (constraints: foreign keys, uniqueness, checks: hold before and after), Isolated (concurrent transactions can't see each other's half-done work), Durable (committed means fsynced). The practical consequence: entire categories of bugs live in the database instead of in your code. No application-level "check then insert" races for uniqueness; no half-transferred money; no orphaned order items: the schema itself is an always-on test suite. This is why the boring default answer to "which database?" has been PostgreSQL for a decade: you give up ACID and joins only when a specific access pattern forces you to, which is what the next five lessons are about.

Where it strains: objects must be shredded into rows on write and reassembled on read (the "object-relational impedance mismatch" that ORMs paper over); schema changes on huge tables need care; heavy write throughput fights B-tree random I/O; and horizontal scaling was bolted on decades later (though modern "NewSQL": Spanner, CockroachDB, Aurora's storage layer from the replication course: rebuilds relational guarantees on distributed foundations). None of these strains are fatal; all of them created the species in the lessons ahead.

Check yourself

1. A products table stores supplier_name and supplier_phone on every product row. The supplier changes phone numbers. What category of problem is this, and what's the cure?

Same disease as the lab's Design A. A transaction can update all N copies atomically, but nothing guarantees the app finds all N: and next month someone adds a code path that doesn't. Normalization makes the inconsistency unrepresentable: one row, one phone, every product join sees the same truth.

2. A table takes 40,000 INSERTs per second and is queried once a day for a report. A well-meaning engineer adds 8 indexes to make the report instant. What happens?

Indexes are copies, and copies must be maintained synchronously. The read/write trade is the deepest dial in storage engines: this lesson's B-trees favor reads; Lesson 4's LSM trees flip the dial toward writes. For a once-a-day report, the right answer is usually: no index, let it scan.

3. Two requests simultaneously check "is username 'alex' taken?": both see no: and both insert it. In a relational design, where should the defense live?

Check-then-act is a race no application-side pattern fully closes (there are two app servers; a mutex on one doesn't bind the other). The constraint is enforced inside the engine's transaction machinery, where the race is decidable. Rule of thumb: invariants about data belong in the schema, not the code: it's the relational model's core gift.