NO SQL Lesson 6 – ACID vs BASE | Dataplexa
NoSQL Fundamentals · Lesson 6

ACID vs BASE

A user clicks "Pay Now" on a checkout page. Their browser is slow — they click again. Two identical payment requests hit your server at the same millisecond. Your database processes both. The customer is charged £340 twice. Their bank account is short £340. Your support queue explodes. This is not a theoretical problem — it happens to real companies every week. The difference between a system where this is impossible and one where it happens daily comes down to one choice: ACID or BASE.

ACID — Four Guarantees, Each One Critical

ACID is a set of four properties that a database transaction must have to be considered reliable. Each letter protects you from a specific class of failure. Lose any one of them and data corruption becomes possible.

A

Atomicity — All or Nothing

A transaction is a single unit. Either every operation inside it succeeds — or none of them do. There is no "half done."

Real consequence: You transfer £200 from Account A to Account B. This is two operations: debit A, credit B. If the debit succeeds but the server crashes before the credit — atomicity rolls everything back. Account A keeps its £200. Nobody loses money into thin air.
C

Consistency — Rules Always Hold

A transaction can only bring the database from one valid state to another valid state. It can never break your defined rules.

Real consequence: Your products table has a rule: stock can never go below 0. If two customers simultaneously buy the last item, consistency ensures only one succeeds. The other gets an error. Stock never reads -1.
I

Isolation — Transactions Don't See Each Other

Concurrent transactions execute as if they were running one at a time. A transaction in progress is invisible to all other transactions.

Real consequence: 500 users all read your product inventory at the same time while you're in the middle of updating it. Without isolation, some users see the half-updated value. With isolation, they all see either the old value or the new value — never the in-between state.
D

Durability — Committed Means Saved

Once a transaction is committed, it stays committed. A power cut, a server crash, a kernel panic — none of it can undo a committed write.

Real consequence: A customer's order is confirmed, the email is sent, the payment is taken — then the server dies 1 second later. When it restarts, the order is still there. Durability is what makes that promise safe to make.

ACID in Code — A Real Bank Transfer

The scenario: You're building the transfer feature for a fintech app. User A sends £200 to User B. This requires two database writes — debit A, credit B. Here's how ACID protects this in PostgreSQL:

-- Start the transaction — nothing is saved until COMMIT
BEGIN;

-- Step 1: Debit the sender
UPDATE accounts
SET balance = balance - 200
WHERE user_id = 'user_A'
  AND balance >= 200;  -- consistency check: can't go negative

What each line does:

BEGIN;

Opens a transaction. From this point, all changes are held in a temporary state — visible only to this connection, not yet permanent. If anything fails, everything since BEGIN is discarded.

AND balance >= 200

This is a consistency guard — the UPDATE only runs if the sender has enough money. If balance is £50 and they try to send £200, zero rows are updated. The transaction is then rolled back. No overdraft possible.

-- Step 2: Check that Step 1 actually updated a row
-- If 0 rows were updated, the sender didn't have enough balance
-- ROLLBACK undoes everything — atomicity in action
GET DIAGNOSTICS row_count = ROW_COUNT;
IF row_count = 0 THEN
    ROLLBACK;
    RAISE EXCEPTION 'Insufficient balance';
END IF;

-- Step 3: Credit the receiver
UPDATE accounts
SET balance = balance + 200
WHERE user_id = 'user_B';
ROLLBACK;

Atomicity in action. If Step 1 found insufficient balance, ROLLBACK cancels the entire transaction. The debit never happened. user_A's balance is unchanged. user_B gets nothing. The system is in exactly the state it was before.

UPDATE accounts SET balance = balance + 200

This write is still inside the transaction — still not permanent. It's isolated from every other connection reading the accounts table right now.

-- Step 4: Make it permanent — both writes saved atomically
COMMIT;
-- Before transfer:
user_A balance: £500
user_B balance: £100

-- After COMMIT:
user_A balance: £300   (-£200)
user_B balance: £300   (+£200)

-- Total money in system: £600 → £600   ✓ (no money created or destroyed)

COMMIT — all four ACID properties at once:

Atomicity: Both updates committed together — impossible for one to succeed without the other.

Consistency: Total balance in the system stayed at £600 — money was neither created nor destroyed.

Isolation: No other connection saw the in-between state where A was debited but B wasn't yet credited.

Durability: The committed balances survive any crash, power cut, or restart.

What Happens Without ACID — The Race Condition

The scenario: Same transfer — but this time there's no transaction. Two checkout requests for the same item arrive at exactly the same millisecond. Watch what breaks:

❌ Without transaction (broken)

-- Request 1 reads stock: 1 item left
SELECT stock FROM products
WHERE id = 'ITEM_99';
-- Returns: 1

-- Request 2 ALSO reads stock: 1 item left
SELECT stock FROM products
WHERE id = 'ITEM_99';
-- Returns: 1

-- Both think 1 item is available
-- Both proceed to place the order
-- Both decrement stock
UPDATE products SET stock = stock - 1
WHERE id = 'ITEM_99';
-- stock is now: -1

✅ With transaction (fixed)

BEGIN;

-- SELECT FOR UPDATE locks this row
-- Request 2 must WAIT until Request 1 finishes
SELECT stock FROM products
WHERE id = 'ITEM_99'
FOR UPDATE;
-- Returns: 1 (locked)

UPDATE products
SET stock = stock - 1
WHERE id = 'ITEM_99'
  AND stock > 0;

COMMIT;
-- Lock released. Request 2 now runs.
-- Sees stock = 0. Zero rows updated.
-- ROLLBACK. "Out of stock" returned.

The race condition explained:

Both read stock = 1 simultaneously

This is called a dirty read. Without isolation, two transactions read the same row at the same time — both see 1, both think they can proceed.

stock ends up at -1

Two orders placed for one item. One customer will receive it. The other will be left waiting indefinitely while your warehouse team scratches their heads. This exact bug caused a famous double-booking incident at a major airline in 2019.

SELECT FOR UPDATE

This is PostgreSQL's row-level lock. The first transaction to run this line owns the lock. Request 2 hits this line and waits — it cannot read or modify this row until Request 1 commits or rolls back. Isolation enforced.

BASE — The Alternative Contract

BASE is not a weakness — it's a deliberate design choice made by NoSQL databases that prioritise scale and availability over strict consistency. It stands for three things:

BA

Basically Available

The system guarantees availability — it always responds. But the response might be a slightly stale value, or a partial result. It never just goes dark. Think of it as: "I'll always give you an answer. I just can't promise it's the very latest one."

S

Soft State

The state of the system may change over time — even without new input. Data is being replicated, synced, and reconciled in the background. The system is always moving toward a consistent state, even if it's not there yet.

E

Eventually Consistent

Given enough time with no new writes, all nodes will converge to the same value. The guarantee is not right now — it's eventually. For most use cases, "eventually" means milliseconds to seconds.

BASE in Code — The Same Transfer, Different World

The scenario: You're tracking user wallet balances in Cassandra for a gaming platform. Points are awarded constantly — thousands per second. Perfect consistency isn't required — if a player sees 4,820 points instead of 4,821 for half a second, nothing breaks. But the system must never slow down or refuse a write.

from cassandra.cluster import Cluster
from cassandra import ConsistencyLevel
from cassandra.query import SimpleStatement

session = Cluster(['node1','node2','node3']).connect('gaming')

Cluster(['node1','node2','node3']) — connects to a 3-node Cassandra cluster. All three hold copies of the data. The driver picks the fastest node for each request.

# Add 50 points to a player's balance
# ONE = only one node needs to confirm — fastest possible write
add_points = SimpleStatement(
  "UPDATE wallets SET points = points + 50 WHERE player_id = %s",
  consistency_level=ConsistencyLevel.ONE
)

session.execute(add_points, ('player_7721',))
-- Write acknowledged immediately by node1
-- node2 and node3 will sync within ~50ms

Result: Applied
Latency: 1.2ms

-- If player reads balance right now from node2:
-- Might see: 4,820  (old value — node2 not synced yet)
-- 50ms later from node2:
-- Will see:  4,870  (synced — eventually consistent)

BASE behaviour explained:

ConsistencyLevel.ONE

This is BASE's "Basically Available" in action. Write confirmed the moment one node saves it. No waiting for the other two. 1.2ms response time instead of the 8–15ms needed to confirm on multiple nodes.

Might see 4,820 then 4,870

This is "Soft State" + "Eventually Consistent." The value is in transition — node2 hasn't received the sync yet. For 50ms, different nodes give different answers. Then they converge. No manual intervention. No rollback. Just time.

No BEGIN / COMMIT / ROLLBACK

Cassandra has no concept of multi-row transactions in the traditional sense. Each write is independent. This is why it can handle a million writes per second — there's no coordination overhead, no lock manager, no transaction log to coordinate across nodes.

The ACID vs BASE Spectrum

It's not a binary switch — it's a dial. Different databases and different settings land at different points on this spectrum:

FULL ACID FULL BASE
PostgreSQL
MySQL

Full ACID
Strong consistency

MongoDB
(w:majority)

Near-ACID
Tunable

Redis
DynamoDB

Configurable
Per-operation

Cassandra
(QUORUM)

Leaning BASE
Configurable

Cassandra
CouchDB (ONE)

Full BASE
Max availability

Notice: Many databases let you choose where you land on this spectrum per-query. MongoDB with w:1 leans BASE. With w:majority it leans ACID. You're not locked in — but you have to choose consciously.

When Each Model Wins — 4 Real Scenarios

Scenario Use Why
Bank transfer — debit A, credit B ACID Partial failure = money disappears. Atomicity is non-negotiable. £1 lost is a regulatory incident.
Social media like count updates BASE Showing 4,201 vs 4,202 likes for 100ms hurts nobody. Locking every like write at Instagram scale would melt the database.
E-commerce inventory — last item in stock ACID Two users buying the same last item simultaneously. Without isolation, both orders succeed and stock goes negative. Overselling is a real business crisis.
IoT sensor readings — 100k devices/sec BASE A temperature reading 200ms stale is irrelevant. Running ACID transactions across 100,000 concurrent writes per second is physically impossible at reasonable cost.

The Consequences of Choosing Wrong

Using BASE where you need ACID

💸 Double charges — payment processed twice during a brief inconsistency window
📦 Overselling — stock goes negative, customers order items you don't have
⚖️ Compliance failure — financial regulators require ACID-level audit trails
🩺 Medical errors — patient records updated partially, clinician sees wrong dosage

Using ACID where you need BASE

🐢 Throughput collapse — lock contention at 50k writes/sec brings the DB to its knees
💰 Massive cost — ACID at global scale requires enormous compute resources
📉 Availability drops — under load, lock timeouts start returning errors to users
🌍 No global distribution — strict ACID across continents means 200ms+ latency per write

Teacher's Note

The most dangerous engineer is the one who learned ACID in university and applies it to everything, or the one who heard "NoSQL is faster" and removed all transaction logic from their payment system. Both are expensive mistakes. The skill is knowing which guarantee each piece of your system actually needs — and matching the database model to that requirement deliberately.

Practice Questions — Spot the Bug

Scenario:

A developer writes this code for a flight booking system:

UPDATE seats SET status='booked' WHERE seat_id='12A';
INSERT INTO bookings (user_id, seat_id) VALUES ('u_99', '12A');

The server crashes between the two statements. The seat is marked as booked but no booking record exists. The seat is now permanently reserved for nobody. Which ACID property was violated?


Scenario:

Two warehouse workers simultaneously scan the same product to update its location. Worker A reads location as "Shelf B4", Worker B reads it at the same millisecond. Worker A updates it to "Shelf C2". Worker B — using the old value — updates it to "Shelf D1". The final location is "Shelf D1" but the item is actually on "Shelf C2". Which ACID property was violated?


Scenario:

You are building a leaderboard for a mobile game. 200,000 players update their scores every minute. The leaderboard refreshes every 30 seconds anyway, so a score being 1–2 seconds stale between refreshes is completely fine. Perfect consistency is not required. Maximum write throughput is. Should you use ACID or BASE for this leaderboard?


Quiz — Production Incident. ACID or BASE Failure?

Incident report:

Your on-call engineer gets paged at 2am. Users are reporting money disappearing from their wallets. Investigation shows: the debit query ran successfully, then a network timeout occurred before the credit query could execute. No transaction wrapper was used. £12,400 is missing across 62 affected transfers. What went wrong?

Incident report:

A junior engineer raises a bug report: "The like counter on posts shows different numbers on different devices for about 2 seconds after someone likes a post. Sometimes my phone shows 841 and my laptop shows 840 at the same time." Your system uses Cassandra with ConsistencyLevel.ONE for like counts. Is this a bug that needs fixing?

Architecture decision:

You're designing an online concert ticket system. 50,000 fans will try to buy the last 1,000 tickets simultaneously when they go on sale. Each purchase requires: deduct 1 from stock AND create an order record. If stock hits 0, all further purchases must fail — no overselling allowed. What is the correct database model for this operation?

Up Next · Lesson 7

Types of NoSQL Databases

A deep dive into all four NoSQL families — key-value, document, column-family, and graph — with real data, real code, and the exact moment each one becomes the obvious right choice.