Mustaque Nadim Academy
Part 2 · Databases

Transactions

On Adda a 'like' must record the like AND fire the notification — a crash halfway can't leave one without the other. Transactions make "all or nothing" real.

The problem

Adda ships a feature: when you like a post, the author gets a notification. In code that is two steps — insert the like, insert the notification. Then Fahim reports something strange: sometimes he sees his like on a post, but the author swears they were never notified. Sometimes it is the reverse. The server crashed — or a shard hiccupped — in the gap between those two lines, and the pair came apart.

Shuvo reaches for the cleanest analogy on the whiteboard: money. Transfer $100 from Alice to Bob — subtract 100 from Alice, add 100 to Bob. If the server crashes in the gap, Alice is out $100 and Bob never got it. The money simply evaporated, exactly like Adda's orphaned like.

It gets worse under concurrency. Two withdrawals hit Alice's $150 balance at the same moment, both read "150," both approve, and she withdraws $200 she never had. Nothing in the individual operations is wrong. The danger lives in the seams between them — the moments where a crash or another request can interleave.

A first attempt

The naive guard is "check before you write": read the balance, verify it is enough, then subtract. But the check and the write are two separate steps, so another request can slip in between them — you have just moved the seam, not closed it.

Tanvir might reach for a lock around the whole operation. Now correctness depends on Adda's code never crashing while holding that lock, never forgetting to release it, and coordinating locks across every path that touches the balance. That is a huge amount of fragile bookkeeping to reinvent for something the database can guarantee for you — if you wrap the steps in a transaction.

The insight

Group the steps into one transaction and let the database enforce ACID: the group either fully happens or fully does not, and concurrent transactions do not corrupt each other.

  • Atomicity: all steps commit, or none do. A crash mid-way rolls back cleanly.
  • Consistency: the transaction moves the database from one valid state to another (constraints hold).
  • Isolation: concurrent transactions behave as if run one at a time — no peeking at half-finished work.
  • Durability: once committed, it survives crashes, because it was written to a durable log first.
BEGIN;
  UPDATE accounts SET balance = balance - 100 WHERE id = 'alice';
  UPDATE accounts SET balance = balance + 100 WHERE id = 'bob';
COMMIT;   -- both, or (on crash) neither

How it works

Write intentions to a log first

Before touching the real data, the database appends the change to a write-ahead log (WAL) — the same WAL Adda's followers replay in replication. If it crashes, recovery replays committed transactions and discards uncommitted ones — this is how atomicity and durability are enforced.

Isolate concurrent transactions

Using locks or multi-version snapshots (MVCC), each transaction sees a stable view and cannot read another's uncommitted changes. This is what stops the two-withdrawal race.

Commit atomically

COMMIT flips the transaction to durable in one indivisible act. Before that point, none of its changes are visible; after it, all of them are.

Choose an isolation level

Stronger isolation prevents more anomalies but allows less concurrency. You pick the weakest level that is still correct for the operation.

Isolation levels trade safety against throughput. From weakest to strongest, and what each still allows:

Isolation levelDirty readNon-repeatable readPhantom read
Read Uncommittedpossiblepossiblepossible
Read Committedpreventedpossiblepossible
Repeatable Readpreventedpreventedpossible
Serializablepreventedpreventedprevented

Concrete numbers

Isolation is not free. On a contended row, Serializable can cut throughput by 2–5x versus Read Committed, because transactions must wait or abort and retry when they conflict. Most systems default to Read Committed (Postgres — Adda's choice) or Repeatable Read (MySQL) and reserve Serializable for the few operations that truly need it.

The distributed case is far more expensive — and this is where Adda's sharding comes back to bite. A two-phase commit (2PC) across 3 nodes needs two network round trips plus durable writes at each participant — tens of milliseconds across a datacenter, hundreds across regions — and it blocks: if the coordinator dies mid-commit, participants hold locks until it recovers. That single blocking property is why large systems avoid distributed transactions and favor idempotent, eventually-consistent workflows (like the saga pattern) instead.

When to use it

The trade-off

Stronger guarantees cost concurrency. Higher isolation prevents more anomalies but serializes more work, and distributed transactions add network latency and blocking on top. Use the weakest isolation that is still correct, and keep transactions short — a long transaction holds locks and starves everyone waiting behind it.

Prefer idempotency over distributed transactions

When the like lives on one shard and the notification on another, a two-phase commit is often the wrong tool — it blocks on coordinator failure. Instead, make each step idempotent and retryable and stitch them with a saga (with compensating actions to undo). You trade strict atomicity for availability, which is usually the right call across Adda's network.

Practice

Recap

  • A transaction makes a group of operations all-or-nothing and crash-safe via ACID.
  • Isolation levels trade anomaly prevention against concurrency — use the weakest that stays correct.
  • Distributed transactions (2PC) give atomicity but block and cost round trips; sagas plus idempotency are the common alternative.

In an interview

When a design touches money, inventory, or bookings, say "this needs a transaction" and name the ACID property at risk. For the multi-service case, resist reaching for two-phase commit — explain that it blocks, and propose idempotent steps with a saga instead. Showing you match isolation level to the operation, not the whole database, is what stands out.

How is this guide?

Last updated on

On this page