The Relational Model & ACID
Name what each ACID guarantee protects against: atomic debit+credit, fsync'd WAL durability, and consistency as your constraint-enforced invariant.
Four guarantees, not one buzzword
ACID is four separate guarantees that people blur into one buzzword. The way to sound senior is to say what each one protects against with a concrete failure in mind.
Atomicity means a transaction is all-or-nothing. A money transfer is a debit on one row and a credit on another. If the process crashes, or a constraint fails, or the network drops between the two writes, atomicity guarantees the database rolls back to the state before the transaction started. Without it you get the classic bug: 100 dollars leaves account A and never arrives at account B because the second write failed. The invariant "total money is conserved" only holds if both writes commit together or neither does.
Consistency in ACID is not a free property the database grants you. It means the database moves
from one valid state to another valid state as defined by your constraints and your application
logic. The database enforces the part you declared: CHECK (balance >= 0), NOT NULL, foreign
keys, unique constraints. Everything else (a transfer must not create money) is an invariant your
transaction plus isolation must uphold. Consistency is the outcome; atomicity, isolation, and your
constraints are the mechanism.
Isolation means concurrent transactions do not corrupt each other. If two transfers touch the same account at once, isolation determines whether one sees the other's half-finished work. This is the hardest and most-probed guarantee, and the next lesson is entirely about its levels.
Durability means once the database returns "committed," that data survives a crash, a power
loss, or a kill -9. The concrete mechanism: the change is written and fsync'd to the write-ahead
log (WAL) on durable storage before the commit acknowledgment is sent. A commit that is only in a
memory buffer is not durable; if the box loses power, it is gone. This is why a synchronous commit
costs a disk fsync (often a few ms), and why "group commit" batches many transactions into one
fsync to amortize it.
Interview nuance: When asked "is your write durable?" the strong answer names the WAL and
fsync, and flags the tradeoff: synchronous_commit = off in Postgres returns faster but risks
losing the last few hundred ms of commits on a crash. Money says on; a like counter can say off.
When is strict ACID worth its cost?
When a violated invariant means lost money, double-charged users, or corrupted balances, pay for it: a single-primary relational database (Postgres, MySQL InnoDB) with real transactions. When the invariant is soft (a view count, a feed ordering) you can relax to BASE (basically available, soft state, eventual consistency) and buy horizontal scale and availability instead.
BEGIN
UPDATE accounts SET balance = balance - 100 WHERE id = A; -- debit
UPDATE accounts SET balance = balance + 100 WHERE id = B; -- credit
COMMIT -- atomic: both or neither; durable: fsync'd WAL before ack
Recap: ACID is four concrete guarantees; atomicity makes debit+credit all-or-nothing, durability means fsync'd to the WAL not just memory, and consistency is your invariant enforced by constraints plus isolation, not a free lunch.
You are about to design a money-transfer feature. Match each failure to the ACID guarantee that prevents it.
Apply
Your turn
The task this lesson builds to.
Design the schema and transaction boundaries for a bank money-transfer feature that must never lose or double-count funds under concurrent transfers.
Think about
- Why must debit + credit be a single atomic transaction?
- What does durability actually mean at commit time?
- When is strict ACID worth the cost versus relaxing to BASE?
Practice
Make it stick
A second problem on the same idea, so it survives past today.
Design the transaction and idempotency strategy for Stripe-style payment intents where the client retries aggressively on timeouts and network blips, at roughly 5,000 charge attempts per second, so no customer is ever charged twice for one intent.
Think about
- Why can the external card charge not live inside your database transaction?
- What database mechanism (not app-side logic) makes two concurrent retries converge on one charge?
- How does recovery reconcile a crash that happened after charging but before recording success?