Two Philosophies for What a Write Guarantees
ACID vs BASE
ACID transaction guarantees and isolation levels versus BASE — basically available, soft state, eventual consistency — and where each philosophy fits.
What you'll learn
- Define each ACID property and what it protects
- Explain isolation levels and the anomalies they prevent
- Contrast the ACID and BASE philosophies and where each fits
There are two philosophies for what happens when you write data. ACID says: guarantee correctness, even if that costs availability and scale. BASE says: stay available and scale, accept that correctness arrives a little late. Neither is “better” — they’re optimized for different problems, and naming which one you’ve picked is half of designing a data layer.
ACID, property by property
ACID is the contract a transaction in a relational database makes:
- Atomicity — all the writes in a transaction commit together or none do. Transfer money: the debit and the credit both land, or neither does. No half-states survive a crash.
- Consistency — the transaction moves the database from one valid state to another, respecting all constraints (foreign keys, uniqueness, checks). (This “C” is about invariants, distinct from CAP’s “C” — same letter, different meaning, a notorious source of confusion.)
- Isolation — concurrent transactions don’t step on each other; the result is as if they ran one at a time, even though they overlapped.
- Durability — once committed, the data survives crashes and power loss (it’s on disk / in the write-ahead log, not just in memory).
Atomicity and durability are usually all-or-nothing. Isolation is a dial.
Isolation levels
Perfect isolation (serializable) is expensive, so databases offer weaker levels that allow specific anomalies in exchange for more concurrency. The classic anomalies, weakest to strongest level that prevents them:
| Level | Dirty read | Non-repeatable read | Phantom read |
|---|---|---|---|
| Read uncommitted | possible | possible | possible |
| Read committed | prevented | possible | possible |
| Repeatable read | prevented | prevented | possible |
| Serializable | prevented | prevented | prevented |
- Dirty read — you read another transaction’s uncommitted (maybe rolled-back) write.
- Non-repeatable read — you read a row twice in one transaction and get different values because someone committed in between.
- Phantom read — you run the same range query twice and new rows appear because someone inserted into the range.
BASE: the other philosophy
BASE is the design stance of large-scale NoSQL systems, an intentional backronym contrasting with ACID:
- Basically Available — the system answers requests (perhaps with stale or partial data) rather than refusing.
- Soft state — state may change over time even without new input, as replicas reconcile in the background.
- Eventual consistency — given no new writes, replicas converge.
BASE trades the hard guarantees of ACID for availability and horizontal scale. It’s the philosophical sibling of choosing AP under CAP and eventual consistency in the model spectrum — the same tradeoff, viewed from the write side.
| ACID | BASE | |
|---|---|---|
| Priority | correctness | availability + scale |
| Consistency | immediate, strong | eventual |
| Typical store | Postgres, MySQL | Cassandra, DynamoDB |
| Best for | money, inventory, orders | feeds, sessions, analytics |
| Under load | may reject to stay correct | stays up, reconciles later |
Where each fits
The decision tracks the cost of being wrong:
- ACID when a stale or lost write is a correctness bug: payments, ledgers, inventory reservations, anything you can’t double-spend or oversell.
- BASE when a stale write is merely a cosmetic annoyance that scale pressure makes worth it: like counts, view counts, activity feeds, recommended items.
And — as ever — real systems mix them: ACID Postgres for the order and payment, a BASE store for the “recently viewed” rail on the same page.
The JavaScript angle
In Node, ACID is something you opt into deliberately with a transaction block — forget it, and atomicity quietly disappears, leaving the half-states ACID exists to prevent:
// ❌ No transaction: two independent writes. A crash or error between them
// leaves the order created but the inventory never decremented.
async function checkoutNaive(db, order) {
await db.query('INSERT INTO orders (id, item) VALUES ($1, $2)', [order.id, order.item]);
await db.query('UPDATE inventory SET qty = qty - 1 WHERE item = $1', [order.item]);
}
// ✅ One ACID transaction: both writes commit together, or roll back together.
// SERIALIZABLE here also stops two checkouts from overselling the last unit.
async function checkout(pool, order) {
const db = await pool.connect();
try {
await db.query('BEGIN ISOLATION LEVEL SERIALIZABLE');
await db.query('INSERT INTO orders (id, item) VALUES ($1, $2)', [order.id, order.item]);
await db.query('UPDATE inventory SET qty = qty - 1 WHERE item = $1 AND qty > 0', [order.item]);
await db.query('COMMIT');
} catch (err) {
await db.query('ROLLBACK');
throw err;
} finally {
db.release();
}
} ACID’s guarantees stop at the boundary of a single database. The hard question — the one that defines modern microservice architecture — is how to get atomicity across services and databases. That’s distributed transactions, next.