Finer merge grains win more automatic merges and cost more complexity
Aliases: grain vs complexity · character-level · finer is not free
What it is
Shrink the conflict unit from whole document to record, field, list element, then character, and the auto-merge success rate rises: two sides’ edits are less likely to land in the same unit. Along the same road, the identities the implementation must keep, the transforms, the layers of diff the UI must draw, also rise. This is a trade to choose in the open, not a progress story of “finer is better”.
Success rate is the share that converges without a human hand. Complexity is protocol, storage, and the cost of a person understanding the diff. Both enter the ledger.
Why it happens
Finer grain slices the address space; commutative writes multiply — that is where the success rate comes from. After the slice, every address needs a stable id: field name, list-item id, paragraph id, down to a character’s site in a CRDT. The id’s lifetime (insert, delete, move, undo) is a state machine of its own; bugs shift from “occasionally drop a section” to “occasionally drop a character, misplace a list item”. The test space explodes with combinations of addresses.
Display follows grain. A record-level conflict is a card gone red; field-level a few cells; character-level red-green chatter inside a line. Human contrast at character grain loses the sentence and sees shards. So products often store fine and aggregate to sentence or paragraph on the UI — the aggregation layer is another complexity bill. Aggregate wrong and already-merged characters reappear as conflict, or characters that should conflict hide inside a “merged” paragraph.
Where it stops holding
Few collaborators, short offline, highly structured data: medium grain (field) is often enough; going finer does not pay back. A product whose job is live co-editing of prose almost owes character grain as a ticket; the complexity is eaten by the editor kernel, and the UI should still present conflicts by sentence or paragraph. A small mobile screen cannot draw character-level diffs; even if storage is fine, display must aggregate. When regulation wants an explainable merge (whose which write entered the final), too-fine grain turns explanation into a character audit, unusable to a person; a layer of business-semantic account is needed. There is a performance knee too: syncing a site per keystroke will kill the product on power and traffic before success rate does.
Applying it
- Pick a default grain per object class first: task cards by field, body by paragraph or sentence, tags by element. Write it into implementation and UI as the same table.
- Storage may be finer than the UI, but aggregation rules must be testable: a unit that auto-merged must not display as conflict.
- Watch success rate and “time for a person to see what changed” together. Optimising only the former pushes grain past readable.
- How to check: the same concurrent edits, run at record, field, and paragraph grain. Note auto-merge share, number of code paths, and time for a participant to point at “who changed what”. If the next-finer rung barely raises success and clearly lengthens pointing time, grain has crossed the line. Do not impersonate this rung’s success rate with LWW’s “there is always one result”.
Related
- Same group: I3.11.1 Field-level merge shrinks what humans have to resolve · I3.11.2 Free text generally cannot be merged by the system · I3.11.4 A merge UI must show where each difference came from, not only the result
- Nearby: I3.04 Sync conflicts · I3.10 Offline and local-first
- Search terms:
merge granularity·conflict grain·CRDT complexity