Skip to content
C.W.K.
Stream
Lesson 03 of 04 · published

One Writer, and One Place the Race Can Happen

~12 min · concurrency, architecture, records, design

Level 0Wet Clay
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

The Two Pains of Parallel Work

Run several sessions at once against a shared body of work and two problems arrive immediately: log races and commit conflicts. They feel like the same problem — two actors, one resource — and they need completely different answers. This lesson is the first; the next track is the second.

The log race has a clean structural answer, and the answer is not locking. Make one process the only writer. Every session, every command-line tool, every user interface is a client that asks that process to write. Sessions stop contending with each other — but notice what actually delivers that, because it is not the process count. The single entry point removes the distributed race; inside the writer, concurrent requests still arrive on a thread pool and their writes are serialized there, behind one lock. What you bought is one place where serialization has to be correct instead of every client having to be.

Why This Is Better Than Locking

Locking is discipline wearing an engineering costume. It asks every writer to acquire correctly, release correctly, and handle the case where they crash while holding — and one writer that forgets, or one path added later that does not know the convention, breaks it silently. The failure mode is interleaved writes that look fine.

Single-writer removes the question. There is no convention for a new client to violate, because clients cannot write at all. The API is the only door, and a door is much easier to keep correct than a rule.

It also removes a whole class of distributed problem that is otherwise nasty. When work happens on more than one machine — a laptop on the road, a workstation at home — a shared file is a genuinely hard synchronization problem and a shared API endpoint is not a problem at all. The traveling machine's tool is an API client, so it logs to the same single writer as everything else, and the question of whose copy is newer never arises.

The Constraint It Puts on the Client

The corollary is worth stating because it gets violated by accident: if the engine is the only writer, the client must never write. Not to the store, not to the log files, not "just this once for a migration." A command-line tool that reaches around its own API to fix something quickly has re-introduced the second writer that the whole design exists to prevent, and it will be the write nobody remembers when the data disagrees six months later.

A pleasant side effect: the client stays tiny. It has no database driver, no schema knowledge, no migration story. It is an HTTP client that formats output nicely, which means it runs anywhere without installation and cannot corrupt anything.

Prefer removing the contention to arbitrating it. Locks, transactions and retries make every writer arbitrate; a single writer leaves exactly one place that has to. The design question to ask first is not "how do these two writers coordinate" but "why are there two writers" — and often the second one exists only because the first was not reachable from where the work happens.

Code

Two architectures, one of which cannot race·text
CONTENDING - every actor writes the store

   session A ---\
   session B -----> [ log store ] <---- interface
   session C ---/         ^
   traveling  ----------/

   needs: a locking convention every writer honors, forever,
          including paths added next year by someone who has
          not read this diagram.
   fails: silently, as interleaved writes that look fine.


SINGLE WRITER - one process, everything else is a client

   session A ---\
   session B -----> [ engine ] --> [ log store ]
   session C ---/       ^
   traveling  --------/

   needs: one lock, in one place. clients cannot write, so
          no convention exists to violate and no new path can break it.
   bonus: the traveling machine is not a special case - it is
          the same client over the same API.


THE COROLLARY, WHICH GETS VIOLATED BY ACCIDENT
   the client must NEVER touch the store directly. not for a
   migration, not for a quick fix, not once. that write is the
   second writer, and it is the one nobody remembers when the
   data disagrees six months later.

External links

Exercise

Count the writers to your most important data store — including migration scripts, admin consoles, one-off repair scripts, and anything with credentials in a configuration file somewhere. For each writer that is not the primary application, write down why it exists. Most will turn out to exist because the primary path did not expose something, and that missing endpoint is the actual fix.
Hint
Repair scripts are the most common second writer and the hardest to remove, because they exist for situations the API deliberately does not support. The useful move is usually to give the API an explicitly-named, logged, hard-to-invoke administrative operation rather than to keep a script that writes silently.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.