Two people can insert text at the same position before either sees the other's change. A collaborative editor needs a rule that makes their copies converge, and a separate rule for when an edit counts as safely stored.
This walkthrough uses a sequence CRDT, a data structure designed to merge concurrent changes. Operational transformation with one sequencer per document is another strong approach. Scale figures come from the question's brief; the batching intervals and snapshot thresholds below are example policies to test.
Define the editor's contract
Users create documents, open current content, edit together, see named cursors and keep writing offline. Reconnecting must merge their changes without replacing everyone else's work. Sharing permissions, comments, search and export are out of scope; formatted text is in scope.
The brief gives a billion stored documents and two million open at the busiest hour. Most have one to five editors at once; a few have up to 100. A steady typist makes about five edits a second, and most document text stays under one megabyte. A document edited daily for years can still have millions of past edits.
Make three separate promises. Your own typing appears immediately. Collaborators in the same region usually see it within about 200 ms. An edit the server has acknowledged survives a server crash. A local edit made offline has no server acknowledgement yet, so show whether changes are local, syncing or saved instead of one ambiguous status.
Give inserted text stable identities
Character offsets become ambiguous when another person inserts text before an edit arrives. A sequence CRDT gives inserted content stable identities and records how pieces relate to each other. Every compatible replica merges operations with the same deterministic algorithm.
Say Ana and Ben both insert a character between the same two existing characters. Their inserts carry different identities. The algorithm orders them the same way on every replica, so once both updates arrive, both clients have the same two characters in the same order. Neither client replaces the whole document with its local text.
Use a maintained implementation such as Yjs with a suitable rich-text editor binding. Do not invent a sorting rule and assume it handles concurrent deletes and formatting. The Yjs documentation says its updates can be applied more than once and in any order, and describes state vectors for exchanging missing changes.
Convergence does not make two offline rewrites of the same sentence read well. Both people's edits can survive while the sentence still needs a human to fix it. The product keeps both people's work. It cannot promise the merged text says what either of them meant.
Separate documents, durable updates and presence
Store document metadata, durable update records and periodic merged snapshots. Each update has a document ID, a stable update ID, a client identity, a payload and a server log position. The log position marks what has been persisted; the CRDT identities decide how text merges.
The conceptual API is:
POST /documentscreates a document and returns its ID.GET /documents/{id}returns metadata and a snapshot reference or initial synchronisation data.- A persistent connection joins a document, exchanges state vectors and missing updates, submits edits and receives durable acknowledgements.
- Presence messages carry a display identity and cursor or selection anchors, and never enter the durable edit log.
Assume authenticated connections and document access checks. Bound document size, update size and message rate so a malformed client cannot use unbounded resources.
Open with a snapshot and close the subscription gap
The document API loads a snapshot at log position S and the durable tail after S. A snapshot holds the mergeable CRDT state. A text-only export would lose the identities that later merges need.
Joining the live room must not leave a gap between that read and the subscription. One approach subscribes first, buffers live events, records a durable high-water mark, and replays the missing tail up to that mark. Then it drains the buffered events and follows the live stream. Duplicate deliveries are harmless to the CRDT, and the update ID also lets storage drop duplicates.
The state-vector exchange covers the general case of which changes each side is missing. A lost pub/sub notification must not lose an update for good. Room workers reconcile against the durable log, and reconnecting clients run synchronisation again.
Acknowledge only after persistence
An editor applies a local change immediately and queues its binary update in local persistent storage. It sends the update with the same stable ID on every retry.
The sync service appends the update to a replicated durable store, then acknowledges it and publishes it to other gateways. A unique constraint on document ID plus update ID means a retried request returns the existing stored result. If an append succeeds but its acknowledgement is lost, resending does not create a second logical edit.
Batching appends can cut write overhead, but acknowledgements and broadcasts to other clients wait for the batch's durable commit. Holding edits in memory for five seconds and acknowledging them straight away would break the server-crash promise. The local editor stays responsive while that acknowledgement is pending.
The durable log is the source of truth, and pub/sub only speeds up delivery. If a sync worker dies after committing but before publishing, another worker or relay reads the committed log and delivers the update. Persisting before broadcasting also means collaborators never see content that a single server crash then loses.
Reconnect without overwriting the shared document
While offline, the client keeps its CRDT state and queued updates on the device. After reconnecting, it exchanges state vectors, receives the remote changes it missed, and sends the local changes the server lacks. The merge uses the stored identities, so it does not depend on character offsets still matching the server's copy.
Browser storage is not a durability guarantee. Devices get lost and users clear local data. Show unsynced status clearly, and never claim server backup for work that exists only on a disconnected device.
Long offline periods constrain compaction. Keep enough merge information for the older clients you support. If a format migration or retention rule makes an automatic merge unsafe, keep the offline work as a separate recoverable copy and ask the user to reconcile it. Silently replacing it with the latest server text would lose their work.
Snapshots shorten replay, but CRDT metadata still grows
Create snapshots in the background after a bounded number of updates or a set time. For example, start at about 1,000 updates, then measure the actual bytes and reconstruction cost. Large updates and idle workers mean a count alone does not guarantee a one-second open.
The worker records the exact log position it included, writes an immutable snapshot, verifies it, then atomically publishes the snapshot pointer. Only after that can the covered log segments be archived under the retention policy. A crash before the pointer is published leaves an unused object, not a missing document.
Monitor the serialised CRDT size as well as the visible text size. Merge metadata and information about deleted content can grow differently from the displayed document. A snapshot does not by itself make garbage collection safe for every offline client. Test the chosen library's retention and migration behaviour instead of deleting old structures because they are invisible.
Keep cursors cheap and anchored to text
Presence is best effort. Keep the latest state in memory, throttle updates and batch delivery, and do not durably append every mouse movement. The Yjs awareness protocol is a separate mechanism for sharing presence state.
A raw cursor offset drifts when text before it changes. Send an anchor tied to the shared document structure and resolve it against each local copy. Yjs provides relative positions for this. Clear disconnected users after a timeout instead of keeping their cursors forever.
Scale connections and fan-out separately
Spread documents across sync workers by document ID, and let gateways manage network connections. A CRDT lets several workers receive a document's updates as long as they reliably share durable changes. Starting with one active room owner per document can still make caching and subscriptions simpler.
At 100 editors making five edits a second, a document produces 500 edits a second. Sending each edit separately to 99 peers is about 49,500 deliveries a second. Small broadcast batches, such as one per recipient every 50 ms, cut message overhead and stay within the latency budget. That is arithmetic on the scenario, not a capacity figure for one machine.
Measure concurrent connections, active edit rate, document memory and fan-out before choosing server counts. Most open documents are not being edited at any given moment. Load cold rooms lazily, evict idle state safely, and apply backpressure to slow clients, which can resynchronise later.
An OT design has smaller positional operations and one total order, but needs correct transformation functions and a sequencer that stays protected through failover. The CRDT approach makes offline merging straightforward, at the cost of larger state and careful compaction. Justify your choice with the cases that test it: concurrent inserts, a lost acknowledgement, and a client reconnecting after weeks offline.
Explain a reconnect after weeks offline
Practise the collaborative editor question in Grind. Talk through two concurrent inserts, a server crash after an append, and a client returning after weeks offline. Say what "saved" means at each point, why a snapshot must keep more than visible text, and why you chose a CRDT or OT.


