Step 1 of 3 · Reading · ~3 min
Atomicity Through Write-Ahead Logging
Transactions & ACID
Atomicity — All or Nothing via WAL
Atomicity means a transaction either applies completely or not at all — there's no such thing as "half a transfer happened." You already built a write-ahead log (WAL) for crash durability; this lesson reuses it as the mechanism for transaction atomicity, tying two ideas together: grouping writes under a transaction id, and undoing or redoing them based on whether the transaction committed.
Why WAL is the natural fit
A WAL is just an append-only sequence of log records: SET key=value, DELETE key, etc., each written to disk before (or as part of) the change being applied. Because it's ordered and durable, it's the perfect place to also record transaction boundaries:
BEGIN 1
WRITE 1 balance_a 900 -- logged with txid=1
WRITE 1 balance_b 1100 -- logged with txid=1
COMMIT 1 -- logged with txid=1
If the process crashes between the two writes, recovery replays the log and can see that transaction 1 never reached COMMIT — so its writes must be undone, not kept.
Building the pieces
- BEGIN — allocate/accept a transaction id and start buffering (or directly logging, tagged with that id) the writes that follow.
- Writes tagged with a txid — every WAL record needs to carry which transaction produced it, e.g.
(txid, key, old_value, new_value). Storing the old value (before-image) is what makes rollback possible without replaying the whole log from the start. - COMMIT — append a
COMMIT <txid>marker to the WAL. Only after this record is durably written is the transaction considered "happened." - ROLLBACK — walk the WAL entries for that txid in reverse order and undo each one (restore the old value, or delete a key that was newly inserted). Reverse order matters if the same key was written multiple times within the transaction — you must peel changes off like a stack, not apply them in forward order.
Crash + Recover semantics
This is the payoff: after a CRASH (simulated) and RECOVER, your recovery routine scans the WAL from the beginning and reconstructs which transactions have a matching COMMIT record:
- Committed transactions (WAL shows
BEGIN ... COMMIT) → replay their writes forward into the table, so the effects are restored even though the in-memory table was lost. - Uncommitted transactions (WAL shows
BEGINwith no matchingCOMMIT, e.g. the crash happened mid-transaction) → their writes must not appear in the recovered table. Effectively, treat them as if rolled back — just don't reapply them during replay.
This two-pass structure — first scan to classify each txid as committed or not, then a second pass (or filtered replay) that only applies committed transactions' writes in order — is exactly what real database recovery (ARIES-style, simplified) does.
Edge cases to watch
- A transaction that writes the same key twice: rollback must restore the value from before the transaction started, not the intermediate value.
- Multiple interleaved transactions in the WAL (txid 1 and txid 2 writes interleaved) — recovery must track state per key correctly by replaying entries in log order, filtered by which txids committed.
ROLLBACKon a transaction that already committed, or committing twice, should be treated as an error rather than silently corrupting state.
Nail this and you have real crash-safe atomicity: the durability guarantee from your WAL lesson, now combined with the all-or-nothing guarantee that makes transactions trustworthy.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…