Module 7 · Dive 7.1
The VolumeServer.
The write path is one GenServer. Many clients commit at once, but a GenServer has a single mailbox and runs one message at a time — so concurrent commits become a strict order for free. Each is checked with OCC (Module 3): if the base it read still matches the head, it appends; otherwise it’s a conflict and the client retries. No lock, because the mailbox already is one. Enqueue from two clients and process.
Concurrent in, ordered out
interactive{:error,{:conflict, head}} — resolved by order, not by a lock.The mailbox is the lock
store.design.mdPeople reach for a mutex to protect a shic counter; on the BEAM you reach for a process. Because a GenServer dequeues and runs one message to completion before the next, there is never a moment where two commits inspect the head at once — the serialization is structural, not enforced. So the OCC check inside handle_call is a plain comparison: does this commit’s base still equal the head? If yes, append the page to CubDB and bump the head; if no, reply {:error,{:conflict, head}} and let the caller re-read and retry. Two clients that both based on LSN 4 cannot both win: whoever the mailbox put first takes LSN 5, the other conflicts. One writer process per volume, no locks, and the strict order Module 3 assumed — that is the whole write path. Next, the read path, which never blocks on this at all.
References & sources
- Echo records
- store.design.mdVolumeServer, write-lock via mailbox, OCC
- graft.design.mdsingle-writer serialization per volume
- External
- GenServerone message at a time
- Optimistic concurrency controlbase-version check on commit
- OTP design principlesprocesses as serialization