Build a graph database (Neo4j / Dgraph-style) (16 scenes)
Scene 10 · ACID on a single primary
A single-primary graph store gives full ACID — a write-ahead logical log for durability, write locks held to commit, and deadlock detection via a wait-for graph — at the cost of write throughput bounded by one machine.
Previously
A lock was just one ingredient; the full promise is ACID — a logical log that survives crashes, write locks held to commit, deadlock detection on a wait-for graph. One primary gives you all of it, cleanly. The catch is in 'one primary': every write funnels through a single machine. To go bigger, we have to split the graph — and that's where everything we've built starts to hurt.
Scene 10
ACID on a single primary
Diagram
One transaction, T1, performs a multi-edge write (Alice follows Bob AND Carol — both inserts must land together). TOP lane: T1's ops, each taking a write lock on the node whose relationship chain it mutates. MIDDLE: the write-ahead logical log — every change is appended here AND made durable on disk BEFORE the commit marker. A crash bolt marks where the power fails; on restart, recovery replays only the durable entries, so a write whose COMMIT never reached disk vanishes — that is ACID, made real by the write-ahead logical log. BOTTOM: a wait-for graph — when two transactions each hold a write lock the other wants, the cycle is a deadlock, and write-lock deadlock detection aborts a victim to break it. A 'bounded by one primary' note: every write funnels through this single machine.
Watch one transaction. Alice follows Bob and Carol — two relationship inserts that must commit together. Before T1 touches the store, it appends each change to a write-ahead log and takes a write lock on the node it mutates. Then the power fails MID-commit — the COMMIT marker never reaches disk. On restart, recovery replays only the entries that were made durable, so the half-finished write simply vanishes and the graph is back to a consistent state. The change either fully happened or it didn't.
Implementation
Transaction.commit
log every change durably, THEN write the commit marker
1def commit(txn):2 for change in txn.changes: # the multi-edge write3 log.append(change) # write-ahead: log first4 log.fsync() # make the changes durable5 log.append(COMMIT, txn.id) # the commit marker6 log.fsync() # durable commit = it survives7 apply_to_store(txn.changes) # only now touch the store8 release_locks(txn) # held until commit
LockManager.acquireWrite
one exclusive write lock per node, held until commit (isolation)
1def acquire_write(txn, node):2 while node.write_lock.held_by_other(txn):3 txn.wait_for(node.write_lock.owner) # edge in wait-for graph4 block_until_freed_or_aborted() # may be the deadlock victim5 node.write_lock.grant(txn) # exclusive: no interleaving6 txn.held_locks.add(node) # held until commit/abort
Not sure what to ask? Tap a question — the staff engineer answers in the chat panel.