Don't block The Rock — relationship-chain locks and dense-node grouping for supernodes
Adding an edge to a celebrity used to take a lock on the entire node, so every concurrent writer serialized and throughput collapsed — until relationship-chain locks let writers lock only the part of the chain they touch, and dense-node grouping (≥50 relationships get a grouped layout by type/direction) made it scale.
Expanding through a supernode was a read disaster; writing to one was a concurrency disaster — a whole-node lock turned every concurrent edge-add into a queue. Relationship-chain locks and dense-node grouping let writers work on different parts of the chain in parallel. Locks, of course, only matter because the database promises something about correctness — and we haven't yet said what.
Scene 09a
Don't block The Rock
- Watch
- Try it
- Predict
- Capture
The Rock has tens of millions of followers. Right now, eight users tap 'Follow' in the same instant — each is one tiny write: add a FOLLOWS edge to The Rock's relationship chain. Watch the lock overlay. The first writer grabs a lock on the WHOLE node, and every other writer — even though it only wants to add its own separate edge — has to wait in line behind it. Eight independent edge-adds, executed strictly one at a time.
Highlighted lines are the ones running in the diagram right now.
def add_follows(writer, target): # target = The Rocknew_rel = RelRecord(writer, target, FOLLOWS)# to splice into the chain you must lock what you touchlock = acquire(target, new_rel) # ← the contended stepsplice_into_chain(target, new_rel) # re-point 2 pointers, O(1)commit_and_release(lock)# the splice itself is tiny; the WAIT for the lock is the cost.# 8 writers, each wanting its own edge, all call acquire() here.
def acquire(node, new_rel):if not CHAIN_LOCKS:# historical: the protected region is the WHOLE nodereturn lock_whole_node(node) # 1 holder at a time# chain locks: protect only the segment being splicedsegment = chain_segment_for(node, new_rel)if DENSE_GROUPING and group_size(segment) >= 10:segment = relax_to_group(segment) # many adders share itreturn lock_segment(segment) # disjoint → no wait
Where this sits in Build a graph database (Neo4j / Dgraph-style)
Scene 09a of 16, in the It breaks act — The supernode shatters the O(1)-per-hop promise.. Concurrent edge-adds to a supernode historically serialized on a whole-node lock; relationship-chain locks plus dense-node grouping let writers touch different parts of the chain at once, turning a serialized hotspot into parallel throughput.
Up next. We just leaned on 'a lock' to make concurrent writes correct. But locks are one piece of a bigger promise: that a multi-step write either fully happens or doesn't, survives a crash, and is isolated from other writers. On a single machine a graph store can give you that fully — ACID — and it's worth seeing exactly how before we try to spread the graph across machines.
All 16 scenes in Build a graph database (Neo4j / Dgraph-style) · Every curriculum