Hermes Wiki
Architecture/Challenges/design-a-real-time-collaborative-document-editor

Design a Real-Time Collaborative Document Editing System

Scenario prompt

Build the sync engine behind a Google-Docs-style editor: multiple users edit the same document concurrently, keystrokes need to feel instant (sub-100ms perceived latency) even under network jitter, and offline edits must merge cleanly when a user reconnects after minutes or hours away. You also need live cursors/presence, and undo/history has to behave sanely even when edits from several people interleave. Scale: millions of documents, the overwhelming majority edited by one to five concurrent users, but a small number of "everyone's in the meeting doc" documents spike to hundreds of simultaneous editors.

Mihir's attempt

[!todo] Write your own attempt here before reading the model solution below — how you'd guarantee two users' concurrent edits converge to the same final document without a central lock, and how you'd handle a document that suddenly has 300 concurrent editors.

Model solution

Pick a convergence mechanism before anything else — CRDTs over Operational Transformation for this scenario. Both solve the same problem (merge concurrent edits deterministically), but they make different bets. OT (the actual mechanism behind early Google Docs) requires a central server to sequence and transform every operation against every other in-flight operation — tighter memory footprint, but the server is a single coordination point and offline support is bolted on, not native. CRDTs (Conflict-free Replicated Data Types) make every edit an operation that's commutative and associative by construction, so any two replicas that have seen the same set of operations converge to the same state without a central sequencer — at the cost of per-character metadata (tombstones, unique IDs) that grows the document's storage footprint over its lifetime. Given the offline-merge requirement in this scenario, CRDT is the better bet: offline support falls out of the data structure for free instead of being a special case to design around.

Route every edit through a per-document relay, sharded by consistent hashing. Each keystroke becomes a small CRDT operation broadcast over a persistent connection (WebSocket) to every other client editing that document. Rather than a single global broadcast fabric, assign each document to a shard via consistent hashing on document ID, so every editor of a given document lands on the same relay node and the fanout stays local to that shard — this is the same reasoning Database Sharding Strategies uses to keep hot keys from overloading a single node, just applied to a WebSocket fanout instead of a database write path. The relay layer itself uses the org's event streaming backbone to persist and replay the op stream, not just broadcast it live.

Separate the durable op log from ephemeral presence signals. Cursor positions and "who's currently viewing" indicators change many times a second per user but carry zero long-term value — they don't belong in the same durability tier as the document's actual edit history. Route presence over a low-durability, fire-and-forget channel (last-write-wins, no replay guarantee) so a presence storm during a 300-person meeting-doc spike can never back up or threaten the durability of the actual CRDT op log, which does need reliable delivery and ordering.

Snapshot and compact the CRDT state, the same way an event-sourced system compacts its log. An unbounded op log (plus CRDT tombstones) grows forever on a long-lived, heavily edited document. Periodically materialize the current document state as a snapshot and truncate the op log behind it — new clients load the latest snapshot plus only the ops since, rather than replaying years of history. This is the identical tradeoff Event Sourcing and CQRS makes for any event-sourced aggregate: the log is authoritative, but nobody should have to replay all of it to get current state.

Degrade gracefully for the hot-document spike instead of scaling per-keystroke broadcast to hundreds of editors. Below some concurrency threshold, broadcast every op immediately for the lowest possible latency. Above it, batch ops into small windows (tens of milliseconds) and/or fall back to periodic full-state reconciliation for clients that fall behind — the same back-pressure instinct as rate limiting a hot key, trading a small amount of added latency for the relay node staying healthy under a load pattern that's rare but real.

Gaps to revisit

  • Fine-grained permissions (can editor A see a comment thread editor B can't) intersect awkwardly with CRDT merge semantics — the data structure doesn't naturally know about per-field visibility, so access control has to be layered on top without breaking convergence guarantees.
  • Undo/redo under concurrent edits: "undo my last change" versus "undo the last change to the document" are genuinely different UX contracts, and CRDTs don't hand you either one for free.
  • Long-lived, heavily-edited documents can accumulate CRDT tombstone overhead faster than snapshotting alone reclaims it — at what point does the storage cost justify a more aggressive garbage-collection scheme, and what does that cost in edit-history fidelity?

Principal Engineer Lens

The CRDT-vs-central-sequencer tradeoff in this challenge isn't really about document editors — it's the same decision that shows up in distributed caches, collaborative whiteboards, multi-region counters, and any system where you have to choose between "coordinate through a central authority" and "design the data structure so coordination isn't required." Recognizing that this is one recurring distributed-systems decision wearing different costumes, rather than a bespoke problem each time, is exactly the kind of pattern-matching that reads as Principal-level judgment in a design review — you're not solving "how do documents merge," you're solving "when can we avoid a central sequencer entirely, and what do we pay for that."

Reel Script

Setup: Two people are typing in the same document at the same moment, on flaky wifi, and one of them is about to go offline for twenty minutes. When they're both back online, the document has to converge to one consistent state — with no central server ever having seen both edits at the same time.

Concept walkthrough: Explain CRDTs versus Operational Transformation, why CRDT's commutative-by-construction merge is the natural fit for offline-first collaboration, the consistent-hashing-sharded relay layer, and why presence/cursors get a completely separate, lower-durability channel from the actual edit log.

Real example tie-in: Note that OT is the mechanism early Google Docs actually used, and that CRDTs are the more common choice in newer collaborative tools precisely because of the offline-merge property — walk through what changes in the architecture when you swap one for the other.

Tradeoffs & alternatives: CRDT's per-character metadata overhead versus OT's tighter footprint but central-server dependency; snapshotting to bound CRDT tombstone growth the same way event sourcing bounds log replay cost.

Principal Engineer takeaway: The best distributed-systems wins often come from re-shaping the data structure so a whole class of coordination problem disappears, rather than building a more elaborate coordinator — ask "can the data type make this commute?" before reaching for a lock, a queue, or a leader election.

Hermes Wiki