ACID, Precisely
The Acronym Was Assembled for the Vowels
ACID is the most-recited acronym in databases, and most engineers misremember three of its four letters. That's not a failure of memory — it's baked into the acronym's own history. The properties came first, from Jim Gray's foundational work on transactions in the early 1980s, which named atomicity, consistency, and durability. The word ACID came later: Theo Härder and Andreas Reuter coined it in 1983 (in a paper literally titled Principles of Transaction-Oriented Database Recovery), and to make Gray's properties spell something pronounceable they added a fourth — isolation — for the vowel. So the very shape of the word is a tell: these are not four equal, orthogonal guarantees handed down as a unit. They're a bundle of related-but-different promises, assembled partly for phonetics, and each one means something far more precise than the vibe of "the database is reliable."
That imprecision is where interviews go sideways and where production outages hide. So we're going to take each letter exactly, and we're going to do it against one running example you can hold in your head the whole way: a $100 transfer from Alice to Bob — debit Alice 100; credit Bob 100. It's the oldest example in the book because it's perfect: it has an invariant everyone feels instantly (the money is only ever moved, never created or destroyed — the two balances always sum to the same total), and each ACID letter turns out to defend that transfer from one specific disaster. Get the four disasters straight and you understand ACID better than most people who've recited it for a decade. Everything below is measured — every claim in this lesson was reproduced on a real PostgreSQL 16 instance, and you'll see the actual output.

A + D: Surviving Failure (the Write-Ahead Log's Two Halves)
Start with the two letters that belong together, even though the acronym splits them apart: Atomicity and Durability. Both are about surviving a crash — they just guard opposite sides of the commit.
Atomicity is all-or-nothing under failure. Our transfer is two writes; if the server dies between them, atomicity guarantees you never get stuck in the half-state where Alice was debited but Bob was never credited. The whole transaction rolls back as if it never happened. Watch it on real Postgres — begin the transfer, debit Alice, and look at the in-between before crashing:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE name = 'alice';
-- mid-txn: alice = 0, bob = 0 ← the $100 is in limbo; if this were the end, it VANISHED
ROLLBACK; -- the crash / the app dying here
-- after: alice = 100, bob = 0 ← undone, no trace
The mechanism is the undo log: before a change is applied, the database records how to reverse it, so a rollback (or a crash) can walk the changes back. All-or-nothing, guaranteed.
Durability is the mirror image: survival after commit. Once COMMIT returns, the write must survive a crash — even a hard one. We proved it the brutal way: commit a deposit, then kill -9 the entire database process (a simulated power cut, no clean shutdown), and restart:
$ docker kill -s KILL pg # committed +500 to bob, then power lost
$ docker start pg
LOG: database system was not properly shut down; automatic recovery in progress
LOG: redo starts at 0/191F160
LOG: redo done
-- bob = 500 ← the committed deposit survived the crash
The mechanism is fsync of the write-ahead log: COMMIT forces the log to physical disk before it acknowledges, and on restart the database redoes any committed work the data files hadn't yet absorbed. That log line — "automatic recovery in progress… redo starts" — is durability doing its job in the open.
Now see why these two are really one thing. Atomicity is the undo half of the write-ahead log (a crash during → roll back); durability is the redo half (a crash after → replay). Same log, both directions — which is exactly why WAL & Durability: Why Committed Data Survives built both mechanisms at once. A and D are the two ends of the failure axis.
Two precise corrections before we leave them, because both are near-universal:
- "Atomic" does not mean "nobody sees it half-done." The word is overloaded. In a CPU, atomic means indivisible — no other thread observes a partial update; that's a concurrency idea, and in ACID it's the job of Isolation, a different letter on a different axis. ACID's Atomicity is only about failure: all-or-nothing when a crash hits. A single-threaded database with zero isolation still needs atomicity. Keep them apart — conflating them is the number-one ACID mistake.
- "Durable" does not mean "safe forever." Durability is relative to a failure model.
fsyncgets your bytes onto the platter, so they survive a process crash, an OS crash, a power cut. It does not, by itself, survive the disk dying — that needs replication — and it evaporates the moment you turn the sync off: an async commit (synchronous_commit = off) acknowledges from memory and opens a loss window of writes that a crash will eat. (That's the same knob that bought the 17.9× speedup in Write Batching & Buffering — the speed is the loss window.) "Durable" means "as durable as the weakest link you actually paid for."
I: Surviving Concurrency — and the Dial You Didn't Know Was Turned Down
Isolation is the other axis entirely. Atomicity and durability protect your transaction from a crash; isolation protects it from other transactions running at the same instant. The ideal it names is serializability: the outcome of running transactions concurrently should be as if they had run one-at-a-time, in some serial order. If every database delivered that, concurrency bugs wouldn't exist.
Here's the part that surprises nearly everyone: that ideal is almost never what you're actually running. Ask Postgres what isolation level it gives you by default:
SHOW default_transaction_isolation;
--> read committed
Read committed — not serializable. PostgreSQL and Oracle default to Read Committed; MySQL/InnoDB defaults to Repeatable Read. None of those is serializable. Isolation isn't a single guarantee at all — it's a dial with several settings, and your database shipped it turned down, trading strictness for speed. You can turn it up, but you have to ask:
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SHOW transaction_isolation; --> serializable
And the difference is not academic — it's your money. Run two concurrent transfers that each read Alice's balance and write a new one. At the default Read Committed, both read the same 30 and 100 balance: the correct answer is 60** — an entire $30 debit silently evaporated. Now run the identical code at Serializable:
ERROR: could not serialize access due to concurrent update
The second transaction is refused and must retry; no update is lost, and the balance settles on the correct $70. Same code, same interleaving — the only thing that changed was the dial.
So the precise correction (and the single most valuable sentence in this lesson): "ACID" does not mean "serializable." A database can be fully, certifiably ACID-compliant and still let two transactions stomp on each other, because ACID never specified which isolation level — it only says isolation is a knob you can turn. Serializable is opt-in, and it costs. The full catalogue of exactly what leaks through at each setting is the whole next lesson, Isolation Levels & Their Anomalies; what you need here is the shape of it: isolation is the one letter that's a spectrum, and the default sits below the top. (The engine that lets Postgres offer the middle of that dial without ever blocking a reader is the subject of MVCC: Readers Don't Block Writers.)
C: The Letter That Doesn't Belong
Consistency is the odd one out — and the most misunderstood letter of the four. The promise sounds airtight: a transaction moves the database from one valid state to another, committing only legal results. And you can watch the database enforce it. Give the accounts table a rule — CHECK (balance >= 0) — and try to overdraw Alice:
UPDATE accounts SET balance = balance - 150 WHERE name = 'alice'; -- she has 100
ERROR: new row for relation "accounts" violates check constraint "accounts_balance_check"
DETAIL: Failing row contains (alice, -50).
The illegal state is rejected and the whole transaction rolls back. That's C, working.
But look precisely at what the database enforced: only the rule you declared. It knows balance >= 0 because you wrote that CHECK. It has no idea that "the two accounts must always sum to the same total," or that "every order needs a customer," unless you also encode those — as constraints, as foreign keys, or by writing your transaction to do both the debit and the credit. The database supplies the tools (constraints, foreign keys, plus the atomicity and isolation that make them hold); it does not supply the invariant. That's yours to define.
This is why Martin Kleppmann, in Designing Data-Intensive Applications, says consistency in ACID is fundamentally "a property of the application" — and, bluntly, that the letter C "doesn't really belong in ACID" at all. Atomicity, Isolation, and Durability are genuine guarantees the database makes. Consistency is a goal you define and the database merely helps you keep — it's the one thing on the list that the other three exist to protect. Notice that every disaster in this lesson is ultimately a consistency violation: money vanishing, money doubling, a negative balance are all "the data is no longer valid." Atomicity, isolation, and durability are the three mechanisms; consistency is the thing they're mechanisms for.
One more collision to defuse: this is not the C in CAP. As CAP, Properly spells out, CAP's consistency means linearizability — a guarantee about reads agreeing across replicas — while ACID's consistency means application invariants inside a transaction. Same letter, unrelated meanings, endlessly conflated. When someone says "strong consistency," always ask which C.
Break It Yourself
You now have all four letters precisely. The fastest way to keep them is to break each one and watch what fails — so here is the $100 transfer with Atomicity, Consistency, Isolation, and Durability as four switches you can flip.
Arm a disaster and turn its guarantee off. Atomicity off + a crash mid-transfer: the debit lands, the credit never runs, and 0. Durability off + a crash just after commit: the transfer was acknowledged, then the un-fsync'd buffer dies — Bob was told he was paid and wasn't; a committed promise, broken. Isolation off + a concurrent duplicate: both copies read 100 becomes 50, an illegal state nobody forbade. The detail to internalize as you play: each disaster is stopped by exactly one letter. Turn off the wrong three and the disaster still doesn't happen — the clearest possible proof that the four defend four genuinely different things.

Four Letters, Two Axes, One Invariant
Here is the reframe that makes ACID finally click — and it's the thing to walk out of this lesson with. Stop reciting A-C-I-D in order. Group the letters by what they actually do, and the structure appears:
- Two of them survive FAILURE. Atomicity (a crash during the transaction → undo) and Durability (a crash after commit → redo) are the two halves of one write-ahead log.
- One of them survives CONCURRENCY. Isolation — the dial — protects your transaction from other transactions running at the same instant.
- One of them isn't a mechanism at all. Consistency is the invariant — the valid state — that the other three exist to protect, and it's mostly your job to define.
So ACID is not four peers. It's three mechanisms guarding one goal, across two axes — failure and concurrency. Hold that single picture and every myth we corrected dissolves on contact: atomicity isn't isolation (different axes), consistency isn't a database feature (it's your invariant), ACID isn't serializable (isolation is a dial the default turns down), and durable isn't forever (it's relative to a failure model). ACID was never one magic property — it's four separate machines: an undo log, a concurrency control, an fsync, and the constraints you wrote.

Key Takeaways
- The acronym is a bundle, not four equals. ACID was coined by Härder & Reuter in 1983; the properties came from Jim Gray, and isolation was added for the vowel. Take each letter precisely — the imprecision is where outages hide.
- A + D survive FAILURE — the two halves of the write-ahead log. Atomicity = all-or-nothing (undo; a crash during rolls back — measured:
ROLLBACKrestored Alice to $100). Durability = survives-after-commit (fsync + redo; measured: a committed deposit survivedkill -9, log "redo starts…"). Beware: atomic ≠ isolation (failure, not concurrency), and durable is relative to a failure model (fsync ≠ disk-loss-proof; async commit opens a loss window). - I survives CONCURRENCY — and it's a dial turned DOWN by default. Postgres defaults to read committed, MySQL to repeatable read; serializable is opt-in. Measured: the same concurrent transfers lost a $30 debit at Read Committed but were refused ("could not serialize access due to concurrent update") at Serializable. ACID ≠ serializable — the full anomaly menu is Isolation Levels & Their Anomalies.
- C is the odd letter — the invariant, not a mechanism. The database enforces only the constraints you declare (measured: a
CHECKrejected a −$50 balance); the invariant is the application's job (Kleppmann: it "doesn't really belong in ACID"). And ACID-C ≠ CAP-C — different meanings, same letter (CAP, Properly). - The reframe: four letters, two axes, one invariant. Three mechanisms — undo (A), a concurrency dial (I), fsync (D) — guarding one goal you define (C), across failure and concurrency. Next — Isolation Levels & Their Anomalies: we just met the dial; now let's read every notch on it and the exact anomaly that leaks through each.