Design collaborative editing
How do two people edit the same document offline and still agree later?
Where did the khichdi go?
Every Sunday the kitchen fixes next week’s menu plan: what each kitchen cooks each day, so suppliers can be told on Monday morning. Since spring the plan has lived in a planner app, on phones and tablets.
One Sunday two of the planners are on the morning train to a Mumbai partner kitchen. Somewhere in the ghats the signal goes, as it always does. The app lets them keep editing. It saves the plan on the device and sends it when the network returns.
The rice supplier has warned of a short Monday delivery, so one planner, on a phone, changes Monday’s rice to khichdi. Across the aisle, the other, on a tablet, adds Thursday’s thepla. Neither knows the other is typing.
At Lonavala both devices find signal. The phone sends its copy of the plan. Forty seconds later the tablet sends its own. On Monday morning the supplier is told rice. The khichdi was typed and seen on the screen, and it is gone. The app sends the whole plan, and the server keeps whichever copy arrived last.
The first fix sends edits instead of whole plans: “insert ‘ Thu: thepla.’ at position 36”. The next Sunday the two devices finish the trip showing different plans, and one of them reads “Wed: up Thu: thepla.ma.” Work out why before reading on. What did position 36 mean on a device where a word before it had already been deleted?
Two more proposals are on the table. Let one person hold the plan at a time: whoever has it edits, everyone else waits. Or put the plan in Google Docs, which solved this years ago. But on the train there is no network, so nobody can ask for the plan or open a Google Doc. And the kitchens read the plan as dishes and days inside the ordering system, so it has to live there. How can two people edit the same plan on separate devices, without a network, and still end up with one plan?
Pause and predict Last-writer-wins lost the khichdi, and edits by position produced “Wed: up Thu: thepla.ma.” What must the two devices exchange so they end with the same plan?
What must never go wrong?
The train trip gives the first row of the table: an edit made and seen on one device must never disappear unless a person chooses to remove it.
Core entities, in two minutes
A Document (one kitchen’s plan for one week), the Changes made to it (each from one device, numbered), the Devices that make them, the Members allowed to edit, and Snapshots that compact the history.
The estimate that changes the design
Assume each plan gets about 50 edits a day of about 20 characters. Stored the way a sequence CRDT stores text, each character carries an id and a reference to its neighbour; call it 32 bytes per character operation, before compression.
Pause and predict A plan holds about 2 KB of text. If it were kept and edited forever, how much CRDT metadata would one year add?
The obvious version, and how it breaks
The first version treats the plan like any form. The app holds the text, and a save sends all of it.
It is correct for one editor. With two editors apart, each save carries a whole plan built from an old starting point, so the later save erases the earlier one. No error is shown; the plan that was saved first is replaced in full. The second try, replaying the other device’s edits at their original positions, fails differently: positions on one device mean something else on the other once the text has shifted. Both run in the merge lab under “Different days”.
A mental model
- Conflict-free replicated data type (CRDT)
- A data structure whose copies can be changed independently and always reach the same value once they have seen the same changes, in any order, with no coordination.
- For example: A text CRDT gives every character a unique id. “Insert k after character A:7” means the same thing on every copy, however the text around it has moved.
The older answer to the same problem is operational transformation (OT): keep positions, but before applying a remote edit, shift it past the local edits it did not know about. “Insert at 36” becomes “insert at 39” if three characters were inserted earlier in the text. Ellis and Gibbs introduced it in 1989, and Google described Docs resolving concurrent edits this way in 2010, with its servers ordering the operations.
Hold the plan, or merge it?
Checking the plan out to one editor at a time is a real design, used by many document systems: take it, edit, hand it back. The train trip is the argument against it: a planner with no network cannot take the lock.
A lock is right for documents one person owns at a time, and for anything where a merge has no safe meaning, such as a signed invoice. A kitchen’s plan is edited by several people, often offline, and a lost edit costs a day of wrong supplies. A lock held by a phone in a tunnel would stop everyone else from editing until that phone found signal again.
What to remember
- Saving whole documents lets the last save erase concurrent edits silently. Send changes, each with an identity.
- Concurrent edits need two properties: convergence (same text everywhere) and intention (both changes survive). Positions alone give neither.
- OT shifts remote edits past local ones, usually with a server ordering operations. A CRDT gives every element an id, so edits commute and any device can merge.
- Local-first: the device is where work happens; the server stores and relays changes keyed by (device, counter), so resends are harmless.
- Convergence guarantees the same text on every device; it does not guarantee sensible text. Bound the metadata (weekly documents) and let people judge the meaning.
Send changes with identities, never whole documents.
In the real world
Real editors handle much more than the lab’s four scenarios: rich text and formatting marks that must expand or not at their edges, moving blocks, undo that undoes only your own edits, cursors that stay attached to the right character, and compact binary encodings that keep CRDT metadata a small multiple of the text. Production libraries spend most of their effort there.
The core stays what you built: changes with identities, a merge every device computes the same way, and a server that remembers and relays, but does not decide.