advanced 3 min answer

A collaborative whiteboard supports hundreds of concurrent editors on one board. Compare OT and CRDT for a canvas with object hierarchies, and describe board sharding and catching up after ten minutes offline.

mirocrdtoperational-transformcollaborationsharding
Show the full answer Hide the answer

OT versus CRDT for a canvas

Operational transformation sends operations that are transformed against concurrent operations to preserve intent. A central server orders them and each client transforms incoming operations against its own pending ones.

For a canvas: the transformation functions must be correct for every pair of operation types — move against delete, resize against group, reparent against reparent — and the number of pairs grows quadratically with operation types. OT for rich text is famously difficult to get right; for a canvas with hierarchies it is harder.

Advantage: compact operations, a small per-object footprint, and a server that can enforce authority — which matters for permissions and for consistency guarantees.

CRDTs define data types whose concurrent operations commute, so replicas converge without transformation or a central orderer.

For a canvas: it fits well. A board is largely a map of independent objects, and a map with last-writer-wins registers per property handles most editing. Position, size, colour and text are per-property values; concurrent edits to different properties of the same shape merge without conflict.

Costs: metadata overhead per object — version vectors or unique identifiers — which for a very large board becomes significant; and tombstones for deletions, which accumulate and require compaction.

The practical answer

Most modern collaborative canvases use CRDT-style structures, because the canvas data model suits them and because they permit offline editing and peer-to-peer merging without a central transformer.

A hybrid is common and sensible: CRDT semantics for object properties, with a server retained as the authority for ordering, permissions and persistence. The server is not required for convergence and is retained for everything else — which is the pragmatic reading of the debate.

Hierarchy is the genuinely hard part in either model. Reparenting objects concurrently can create cycles — A becomes a child of B while B becomes a child of A — and the structure must either prevent this or detect and repair it deterministically on every replica.

Board-level sharding

A board is a natural unit of isolation: all editors of one board must be coordinated, and boards are entirely independent of each other.

  • One board is owned by one server process at a time, holding its state in memory. All sessions for that board route there.
  • A routing layer maps board to server, with the mapping cached and rarely changing.
  • Ownership transfer on failure or rebalance must guarantee a single owner — a lease with fencing — or two servers accept divergent edits.
  • State persisted asynchronously, with an operation log so recovery replays rather than loses.
  • Very large boards may require partitioning within the board, typically spatially, so that clients subscribe only to the region they are viewing — which is also the rendering optimisation, since a client cannot draw a hundred thousand objects anyway.

Catching up after ten minutes offline

  • A monotonic sequence per board, so the client asks for everything after the last operation it saw.
  • A bounded operation history; beyond it, the server sends a state snapshot instead of a delta. Ten minutes is usually within history; ten days is not, and both paths must exist and both must be tested.
  • Local edits made offline are sent after catching up, merged by the CRDT semantics.
  • Snapshot plus delta, so a reconnecting client fetches a recent snapshot and applies only the operations since — far cheaper than replaying from the beginning of the board's life.
  • The client must render progressively during catch-up rather than blocking, or a long absence produces a frozen application at precisely the moment the user returns.