{"id":2981,"date":"2026-06-18T08:21:21","date_gmt":"2026-06-18T08:21:21","guid":{"rendered":"https:\/\/tucumandevelopers.com\/index.php\/2026\/06\/18\/crdt-vs-ot-choosing-the-right-collaboration-algorithm\/"},"modified":"2026-06-18T08:21:21","modified_gmt":"2026-06-18T08:21:21","slug":"crdt-vs-ot-choosing-the-right-collaboration-algorithm","status":"publish","type":"post","link":"https:\/\/tucumandevelopers.com\/index.php\/2026\/06\/18\/crdt-vs-ot-choosing-the-right-collaboration-algorithm\/","title":{"rendered":"CRDT vs OT: Choosing the Right Collaboration Algorithm"},"content":{"rendered":"<div>\n<div><\/div>\n<div data-article-id=\"3930309\" id=\"article-body\">\n<ul>\n<li>Fundamentals: How OT and CRDT actually work<\/li>\n<li>Trade-offs: Complexity, performance, storage, and latency<\/li>\n<li>Use cases: Which algorithm fits which problem<\/li>\n<li>Implementation considerations and popular libraries<\/li>\n<li>Migration paths and hybrid approaches<\/li>\n<li>Practical Application<\/li>\n<\/ul>\n<p>The choice between <strong>CRDT<\/strong> and <strong>OT<\/strong> defines your editor\u2019s user experience as much as your infrastructure: <em>offline behavior, amount of metadata, and the engineering surface area for correctness and performance<\/em> are all direct consequences of that decision. Make the wrong call and you spend months on transformation edge-cases or years fighting metadata growth and garbage collection.<\/p>\n<p>The problem you&#8217;re trying to solve is deceptively simple on the surface: multiple people editing a document. The symptoms in the codebase are familiar \u2014 wrong ordering on reconnect, invisible edits that later undo other people&#8217;s work, unbounded memory growth, or an architecture that forces every write through a central sequencer. Those symptoms point at a mismatch between the collaboration algorithm you chose and the real constraints (offline needs, scale, schema complexity) of your product.<\/p>\n<h2> <a name=\"fundamentals-how-ot-and-crdt-actually-work\" href=\"#fundamentals-how-ot-and-crdt-actually-work\"> <\/a> Fundamentals: How OT and CRDT actually work <\/h2>\n<ul>\n<li>\n<p><strong>Operational Transformation (OT)<\/strong> is a <em>transform-first<\/em> approach: every user action is expressed as an operation (insert, delete, style-change). When operations arrive out of order they are <em>transformed<\/em> against concurrent operations so that applying the transformed operation yields the same result on every replica. OT implementations typically rely on a server to sequence operations or on a transformation control algorithm that enforces convergence properties. (<a href=\"https:\/\/www.interaction-design.org\/literature\/conference\/proceedings-of-the-1998-acm-conference-on-computer-supported-cooperative-work?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">interaction-design.org<\/a>) (<a href=\"https:\/\/ot.js.org\/docs\/operational-transformation?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">ot.js.org<\/a>)<\/p>\n<\/li>\n<li>\n<p><strong>Conflict-free Replicated Data Types (CRDTs)<\/strong> encode merge logic in the data structure itself. Operations (or states) commute: replicas can apply updates in any order and still converge to the same final state, as long as all updates are delivered. CRDTs come in <em>state-based<\/em> and <em>operation-based<\/em> flavors; sequence CRDTs (RGA, Treedoc, etc.) and JSON\/Map CRDTs are the primitives you\u2019ll see in editors and local-first apps. (<a href=\"https:\/\/pages.lip6.fr\/Marek.Zawirski\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">pages.lip6.fr<\/a>)<\/p>\n<\/li>\n<\/ul>\n<p>Practical examples (JavaScript):<\/p>\n<p>Yjs (CRDT) \u2014 create a shared text and insert locally, immediately reflected in the local state and later merged in the background: <\/p>\n<div>\n<pre><code><span>import<\/span> <span>*<\/span> <span>as<\/span> <span>Y<\/span> <span>from<\/span> <span>'<\/span><span>yjs<\/span><span>'<\/span> <span>const<\/span> <span>ydoc<\/span> <span>=<\/span> <span>new<\/span> <span>Y<\/span><span>.<\/span><span>Doc<\/span><span>()<\/span> <span>const<\/span> <span>ytext<\/span> <span>=<\/span> <span>ydoc<\/span><span>.<\/span><span>getText<\/span><span>(<\/span><span>'<\/span><span>doc<\/span><span>'<\/span><span>)<\/span> <span>ytext<\/span><span>.<\/span><span>insert<\/span><span>(<\/span><span>0<\/span><span>,<\/span> <span>'<\/span><span>Hello \u2014 local, instant, and later reconciled<\/span><span>'<\/span><span>)<\/span> <span>const<\/span> <span>update<\/span> <span>=<\/span> <span>Y<\/span><span>.<\/span><span>encodeStateAsUpdate<\/span><span>(<\/span><span>ydoc<\/span><span>)<\/span> <span>\/\/ binary snapshot<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>Yjs exposes <code>Y.Doc<\/code>, <code>Y.Text<\/code>, and efficient binary updates for transport and persistence. (<a href=\"https:\/\/docs.yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">docs.yjs.dev<\/a>)<\/p>\n<p>ShareDB (OT) \u2014 server-backed OT: clients submit atomic ops; the server records and sequences them and transforms incoming ops as needed: <\/p>\n<div>\n<pre><code><span>const<\/span> <span>ShareDB<\/span> <span>=<\/span> <span>require<\/span><span>(<\/span><span>'<\/span><span>sharedb<\/span><span>'<\/span><span>)<\/span> <span>const<\/span> <span>backend<\/span> <span>=<\/span> <span>new<\/span> <span>ShareDB<\/span><span>()<\/span> <span>\/\/ Server creates document, client submits op:<\/span> <span>\/\/ doc.submitOp([{retain: 5}, {insert: ' text'}])<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>ShareDB implements OT types (e.g., <code>json0<\/code>, <code>rich-text<\/code>) and stores ops in an oplog for replay and persistence. (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>)<\/p>\n<blockquote>\n<p><strong>Important:<\/strong> <em>Both families support optimistic local edits and immediate local feedback.<\/em> The difference is where the conflict-resolution logic lives: the transport\/transform layer (OT) or the data type itself (CRDT).<\/p>\n<\/blockquote>\n<h2> <a name=\"tradeoffs-complexity-performance-storage-and-latency\" href=\"#tradeoffs-complexity-performance-storage-and-latency\"> <\/a> Trade-offs: Complexity, performance, storage, and latency <\/h2>\n<p>Here\u2019s a compact comparison you\u2019ll use in architecture decisions.<\/p>\n<div>\n<table>\n<thead>\n<tr>\n<th>Aspect<\/th>\n<th>CRDT (typical behavior)<\/th>\n<th>OT (typical behavior)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>Correctness model<\/strong><\/td>\n<td>Strong <em>eventual consistency<\/em> via commutative merges; local ops always accepted. (<a href=\"https:\/\/pages.lip6.fr\/Marek.Zawirski\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">pages.lip6.fr<\/a>)<\/td>\n<td>Convergence via explicit transformation rules and sequencing; correctness requires careful transform composition proofs. (<a href=\"https:\/\/www.interaction-design.org\/literature\/conference\/proceedings-of-the-1998-acm-conference-on-computer-supported-cooperative-work?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">interaction-design.org<\/a>)<\/td>\n<\/tr>\n<tr>\n<td><strong>Implementation complexity<\/strong><\/td>\n<td>Conceptually simple (commutative ops), but production-quality CRDTs need careful GC, compact binary formats and high-performance encoding to avoid RAM blow-up. (<a href=\"https:\/\/docs.yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">docs.yjs.dev<\/a>) (<a href=\"https:\/\/josephg.com\/blog\/crdts-go-brrr\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">josephg.com<\/a>)<\/td>\n<td>Hard to reason about and easy to get wrong at scale \u2014 the transform matrix for rich structures grows quickly; however, mature OT stacks exist for text\/JSON. (<a href=\"https:\/\/ot.js.org\/docs\/operational-transformation?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">ot.js.org<\/a>) (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>)<\/td>\n<\/tr>\n<tr>\n<td><strong>Runtime performance<\/strong><\/td>\n<td>Naive CRDTs can be heavy (per-element IDs, tombstones). Optimized CRDTs (Yjs, diamond-types, tuned RGA implementations) can be extremely fast and maintainable. (<a href=\"https:\/\/josephg.com\/blog\/crdts-go-brrr\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">josephg.com<\/a>) (<a href=\"https:\/\/yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">yjs.dev<\/a>)<\/td>\n<td>Typically lower per-op metadata; server transforms are O(k) where k = number of concurrent ops to account for. With a central sequencer you can keep clients thin. (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>)<\/td>\n<\/tr>\n<tr>\n<td><strong>Storage &amp; persistence<\/strong><\/td>\n<td>Must store identifiers \/ tombstones or perform compaction; many CRDT systems expose snapshotting and binary formats to control growth. (<a href=\"https:\/\/docs.yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">docs.yjs.dev<\/a>)<\/td>\n<td>Server keeps an op-log (append-only) that can be compacted into snapshots; easier to reason about retention\/retention policies because you control the server. (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>)<\/td>\n<\/tr>\n<tr>\n<td><strong>Offline &amp; P2P<\/strong><\/td>\n<td>Natural fit \u2014 CRDTs shine for <em>peer-to-peer<\/em> and offline-first models because merges are local and commutative. (<a href=\"https:\/\/pages.lip6.fr\/Marek.Zawirski\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">pages.lip6.fr<\/a>)<\/td>\n<td>Offline requires storing a local op buffer and replay\/transform on reconnect; workable but more engineering to preserve intention and avoid divergence. (<a href=\"https:\/\/ot.js.org\/docs\/operational-transformation?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">ot.js.org<\/a>)<\/td>\n<\/tr>\n<tr>\n<td><strong>Developer ergonomics<\/strong><\/td>\n<td>Working with <code>Y.Doc<\/code>, <code>Y.Text<\/code>, or <code>Automerge<\/code> maps well to local-first thinking; you reason about state, not transforms, but you must understand GC and compaction. (<a href=\"https:\/\/docs.yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">docs.yjs.dev<\/a>) (<a href=\"https:\/\/automerge.org\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">automerge.org<\/a>)<\/td>\n<td>With OT you reason about operations and write <code>transform(opA, opB)<\/code> rules; mature libraries hide much of the pain for standard types (text, JSON). (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>Contrarian, practical insight from production experience: <em>CRDTs are often marketed as the \u201ceasier\u201d option because they sidestep transform algebra; in practice, robust CRDT-based systems require low-level systems engineering (compact binary formats, GC, snapshotting, and careful streaming protocols).<\/em> Real-world benchmarking and engineering work drove Yjs (and similar projects) to highly optimized designs \u2014 not because CRDT theory was trivial, but because implementation and performance are hard. (<a href=\"https:\/\/josephg.com\/blog\/crdts-go-brrr\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">josephg.com<\/a>) (<a href=\"https:\/\/yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">yjs.dev<\/a>)<\/p>\n<h3> <a name=\"latency-and-ux\" href=\"#latency-and-ux\"> <\/a> Latency and UX <\/h3>\n<p>Both models support instant local updates (optimistic UI). Perceived latency comes down to transport and how you show remote edits (cursor smoothing, incoming-change animation). OT often uses a server to <em>serialize and transform<\/em> which simplifies some UX decisions; CRDTs often show remote edits as they arrive and rely on convergence guarantees to resolve order differences. (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>) (<a href=\"https:\/\/docs.yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">docs.yjs.dev<\/a>)<\/p>\n<h2> <a name=\"use-cases-which-algorithm-fits-which-problem\" href=\"#use-cases-which-algorithm-fits-which-problem\"> <\/a> Use cases: Which algorithm fits which problem <\/h2>\n<p>Pick with constraints in mind; here are practical rules of thumb that I\u2019ve applied in production.<\/p>\n<ul>\n<li>\n<p>Pick <strong>CRDT<\/strong> when:<\/p>\n<ul>\n<li> <em>Offline-first<\/em> behavior is a hard requirement (mobile-first apps, intermittent connectivity). CRDTs merge naturally and don&#8217;t require immediate server acknowledgement. (<a href=\"https:\/\/pages.lip6.fr\/Marek.Zawirski\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">pages.lip6.fr<\/a>)<\/li>\n<li>You need <em>peer-to-peer<\/em> sync or want to avoid a single sequencer in the critical path. (<a href=\"https:\/\/yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">yjs.dev<\/a>)<\/li>\n<li>Your application tolerates some extra storage or you can invest in compaction\/GC infrastructure (or use an optimized CRDT like Yjs). (<a href=\"https:\/\/docs.yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">docs.yjs.dev<\/a>) (<a href=\"https:\/\/josephg.com\/blog\/crdts-go-brrr\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">josephg.com<\/a>)<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>Pick <strong>OT<\/strong> when:<\/p>\n<ul>\n<li>Your product already centralizes edits for business reasons (real-time collaborative docs with server-side policies, fine-grained access control, audit logs) and you prefer controlling order at the server. (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>)<\/li>\n<li>You need minimal client metadata and tighter control of storage on the client (thin clients). (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>)<\/li>\n<li>You are integrating with mature OT-based stacks (existing ShareDB\/Quill\/Firepad ecosystem) and want to leverage proven tooling. (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>)<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>Edge cases \/ hybrid moments:<\/p>\n<ul>\n<li>For <em>rich structured editors<\/em> (nested nodes, schema constraints) you\u2019ll often reach for CRDTs that have editor bindings (e.g., <code>y-prosemirror<\/code>) or an OT type designed for your editor (e.g., <code>rich-text<\/code> delta with ShareDB). Yjs provides first-class ProseMirror bindings to keep schemas consistent while providing CRDT benefits. (<a href=\"https:\/\/github.com\/yjs\/y-prosemirror?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">github.com<\/a>)<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h2> <a name=\"implementation-considerations-and-popular-libraries\" href=\"#implementation-considerations-and-popular-libraries\"> <\/a> Implementation considerations and popular libraries <\/h2>\n<p>Your architecture will need several layers: the <strong>collaboration engine<\/strong> (OT or CRDT), the <strong>transport<\/strong> (WebSocket \/ WebRTC \/ WebTransport), the <strong>awareness\/presence<\/strong> layer (cursors, user meta), and <strong>persistence\/compaction<\/strong>. Here are the well-trodden picks and the trade-offs I run through immediately.<\/p>\n<ul>\n<li> <strong>Yjs (CRDT)<\/strong> \u2014 high-performance CRDT, editor bindings for ProseMirror\/TipTap\/Remirror, binary updates, GC\/compaction primitives, many transports\/providers. Good for local-first and peer-to-peer topologies. (<a href=\"https:\/\/yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">yjs.dev<\/a>) (<a href=\"https:\/\/docs.yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">docs.yjs.dev<\/a>)<\/li>\n<li> <strong>Automerge (CRDT)<\/strong> \u2014 JSON-like CRDT with a focus on ergonomics; historically heavier on memory but has seen architectural improvements and Rust\/WASM implementations. Best for apps where JSON-first modeling matters and peer-to-peer is desirable. (<a href=\"https:\/\/automerge.org\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">automerge.org<\/a>)<\/li>\n<li> <strong>ShareDB (OT)<\/strong> \u2014 battle-tested Node.js OT backend; integrates with <code>rich-text<\/code> (Quill <code>Delta<\/code>) and <code>json0<\/code>. Good when you control the server and want a straightforward op-log storage model. (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>)<\/li>\n<li> <strong>ot.js \/ Firepad<\/strong> \u2014 educational and earlier production stacks based on OT; useful if you want a tight OT integration with contenteditable or CodeMirror\/ACE. (<a href=\"https:\/\/ot.js.org\/docs\/operational-transformation?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">ot.js.org<\/a>)<\/li>\n<li> <strong>Fluid Framework<\/strong> \u2014 Microsoft\u2019s approach: not strictly OT\/CRDT; it uses a total-order broadcast and DDS primitives optimized for Microsoft 365 scenarios. Good to study as an architectural alternative (hybrid sequencing + rich DDS semantics). (<a href=\"https:\/\/fluidframework.com\/docs\/v1\/faq?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">fluidframework.com<\/a>)<\/li>\n<\/ul>\n<p>Operational details you must plan for:<\/p>\n<ul>\n<li> <strong>Undo\/Redo semantics:<\/strong> CRDTs provide local-scoped undo managers (Yjs exposes <code>Y.UndoManager<\/code>), but semantics differ from traditional global undo stacks. OT systems typically implement undo as inverse-ops or custom transform logic. (<a href=\"https:\/\/docs.yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">docs.yjs.dev<\/a>) (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>)<\/li>\n<li> <strong>Persistence &amp; compaction:<\/strong> CRDTs need snapshot + compaction strategies; OT needs op-log trimming and snapshotting. Both need a robust plan for versioning and rollbacks. (<a href=\"https:\/\/docs.yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">docs.yjs.dev<\/a>) (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>)<\/li>\n<li> <strong>Connectivity &amp; reconnection:<\/strong> Simulate high-latency, partitioned networks in tests. Test reconnect flows: in OT, you must replay\/transform pending ops; in CRDT, you must be able to accept binary deltas and reconcile. (<a href=\"https:\/\/ot.js.org\/docs\/operational-transformation?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">ot.js.org<\/a>) (<a href=\"https:\/\/docs.yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">docs.yjs.dev<\/a>)<\/li>\n<li> <strong>Measurements:<\/strong> track memory per-document, ops\/sec, size of serialized updates, and GC latency. Benchmarks (open-source CRDT benchmarks and community write-ups) will help set expectations. (<a href=\"https:\/\/josephg.com\/blog\/crdts-go-brrr\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">josephg.com<\/a>)<\/li>\n<\/ul>\n<h2> <a name=\"migration-paths-and-hybrid-approaches\" href=\"#migration-paths-and-hybrid-approaches\"> <\/a> Migration paths and hybrid approaches <\/h2>\n<p>Large products rarely rewrite collaboration layers overnight. Here are practical, low-risk paths I\u2019ve used.<\/p>\n<ol>\n<li>\n<p><strong>Dual-write shadowing (coexistence):<\/strong><\/p>\n<ul>\n<li>Run OT <em>and<\/em> CRDT for the same user flows in parallel (write both systems in production traffic but only read from the old system). Validate invariants and divergence with automated checks. This is heavy but the safest route for mission-critical docs.<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong>Snapshot + replay migration (server-driven):<\/strong><\/p>\n<ul>\n<li>Export authoritative state (server snapshot or op-log).<\/li>\n<li>Construct a fresh CRDT document and <code>applyUpdate<\/code>\/<code>apply<\/code> historical ops as <em>updates<\/em> rather than replaying transforms; verify checksums. Yjs exposes binary update functions for this purpose. (<a href=\"https:\/\/docs.yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">docs.yjs.dev<\/a>)<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong>Incremental roll-forward (feature-flagged):<\/strong><\/p>\n<ul>\n<li>Start routing a subset of new documents to the new engine and monitor. Use read-after-write checksums and telemetry to validate correctness before broader roll-out.<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong>Hybrid architecture (best of both worlds):<\/strong><\/p>\n<ul>\n<li>Use OT for server-authoritative sequencing where strict ordering or server-enforced invariants are required (e.g., transactional edits, permissions), and CRDTs for client-side offline merges or presence data. Microsoft\u2019s Fluid shows an alternative path by using a <strong>total-order broadcast<\/strong> service to provide deterministic sequencing while exposing DDS primitives \u2014 it\u2019s neither pure OT nor pure CRDT but a pragmatic hybrid. (<a href=\"https:\/\/fluidframework.com\/docs\/v1\/faq?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">fluidframework.com<\/a>)<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>Practical snippet \u2014 export a Yjs binary snapshot and apply on another node: <\/p>\n<div>\n<pre><code><span>\/\/ Export<\/span> <span>const<\/span> <span>snapshot<\/span> <span>=<\/span> <span>Y<\/span><span>.<\/span><span>encodeStateAsUpdate<\/span><span>(<\/span><span>ydoc<\/span><span>)<\/span> <span>\/\/ binary<\/span> <span>\/\/ Import on target<\/span> <span>const<\/span> <span>target<\/span> <span>=<\/span> <span>new<\/span> <span>Y<\/span><span>.<\/span><span>Doc<\/span><span>()<\/span> <span>Y<\/span><span>.<\/span><span>applyUpdate<\/span><span>(<\/span><span>target<\/span><span>,<\/span> <span>snapshot<\/span><span>)<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>This is the core mechanism for snapshot-and-restore or for bootstrapping new replicas. (<a href=\"https:\/\/docs.yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">docs.yjs.dev<\/a>)<\/p>\n<h2> <a name=\"practical-application\" href=\"#practical-application\"> <\/a> Practical Application <\/h2>\n<p>A concise working checklist and protocol to choose and implement a collaboration stack.<\/p>\n<ol>\n<li>\n<p><strong>Requirements triage (constrained decision):<\/strong><\/p>\n<ul>\n<li> <em>Offline requirement?<\/em> Write that down and treat it as boolean.<\/li>\n<li> <em>Server-authoritative policies or audit trails?<\/em> If yes, prefer server-aware OT or a hybrid.<\/li>\n<li> <em>Editor type?<\/em> Plain text, rich text, structured JSON \u2014 map to available types (<code>rich-text<\/code>, ProseMirror, JSON CRDT). (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>) (<a href=\"https:\/\/github.com\/yjs\/y-prosemirror?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">github.com<\/a>)<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong>Select engine &amp; library:<\/strong><\/p>\n<ul>\n<li>Prioritize libraries that solve your data model and have production bindings: <code>Yjs<\/code> for ProseMirror\/TipTap, <code>ShareDB<\/code> for Quill\/Delta, <code>Automerge<\/code> for JSON-first local-first apps. (<a href=\"https:\/\/yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">yjs.dev<\/a>) (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>) (<a href=\"https:\/\/automerge.org\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">automerge.org<\/a>)<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong>Design network protocol:<\/strong><\/p>\n<ul>\n<li>Choose between <code>WebSocket<\/code> for client-server and <code>WebRTC<\/code> for p2p. Use providers\/adapters already supported by your library (Yjs has <code>y-websocket<\/code>, <code>y-webrtc<\/code>, etc.). (<a href=\"https:\/\/docs.yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">docs.yjs.dev<\/a>)<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong>Implement local optimistic update path:<\/strong><\/p>\n<ul>\n<li>Local change -&gt; apply to local <code>Doc<\/code>\/model -&gt; render immediately -&gt; broadcast change in background.<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong>Persistence &amp; GC policy:<\/strong><\/p>\n<ul>\n<li>For CRDT: implement compaction, snapshotting, and policies to purge tombstones or summarize history. For OT: define op-log retention and snapshot frequency. (<a href=\"https:\/\/docs.yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">docs.yjs.dev<\/a>) (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>)<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong>Awareness &amp; presence:<\/strong><\/p>\n<ul>\n<li>Implement a small, frequently-updated presence channel separate from document updates. Yjs has an <code>Awareness<\/code> protocol; ShareDB offers <code>presence<\/code> patterns. (<a href=\"https:\/\/docs.yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">docs.yjs.dev<\/a>) (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>)<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong>Testing matrix:<\/strong><\/p>\n<ul>\n<li>Concurrency tests (N clients, M concurrent edits).<\/li>\n<li>Partition tests: edits during a simulated network split and reconciliation afterwards.<\/li>\n<li>Performance tests: large documents, high-frequency edits, paste events, mass undo\/redo.<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong>Telemetry &amp; guardrails:<\/strong><\/p>\n<ul>\n<li>Track ops\/sec, bytes transferred per sync, time-to-convergence, GC runtime, memory per document.<\/li>\n<li>Add circuit breakers for unusually large updates or retention anomalies. (<a href=\"https:\/\/josephg.com\/blog\/crdts-go-brrr\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">josephg.com<\/a>)<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong>Rollout strategy:<\/strong><\/p>\n<ul>\n<li>Pilot on low-risk documents, monitor, then expand with feature flags or tenant gating.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<blockquote>\n<p><strong>Quick protocol example (OT -&gt; CRDT migration runbook):<\/strong><\/p>\n<ol>\n<li>Instrument checksums for every op\/snapshot in the OT server. <\/li>\n<li>For each doc to migrate, snapshot the document and op-log range. <\/li>\n<li>Create a CRDT doc; apply the snapshot and then reapply ops as idempotent updates. <\/li>\n<li>Run diff checks and hold in read-only mode until integrity checks pass.<\/li>\n<\/ol>\n<\/blockquote>\n<p>Sources<\/p>\n<p><a href=\"https:\/\/hal.inria.fr\/inria-00555588\" target=\"_blank\" rel=\"noopener noreferrer\">A comprehensive study of Convergent and Commutative Replicated Data Types (Shapiro et al., 2011)<\/a> &#8211; Formal definition and taxonomy of CRDTs; basis for state-based vs operation-based CRDT reasoning. (<a href=\"https:\/\/pages.lip6.fr\/Marek.Zawirski\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">pages.lip6.fr<\/a>)<\/p>\n<p><a href=\"https:\/\/dl.acm.org\/doi\/10.1145\/289444.289469\" target=\"_blank\" rel=\"noopener noreferrer\">Operational Transformation in Real-Time Group Editors (Sun &amp; Ellis, 1998)<\/a> &#8211; Canonical OT paper describing transform-based convergence and early correctness issues. (<a href=\"https:\/\/www.interaction-design.org\/literature\/conference\/proceedings-of-the-1998-acm-conference-on-computer-supported-cooperative-work?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">interaction-design.org<\/a>)<\/p>\n<p><a href=\"https:\/\/yjs.dev\/\" target=\"_blank\" rel=\"noopener noreferrer\">Yjs \u2014 Homepage<\/a> &#8211; Project overview, claims, and ecosystem; useful for understanding Yjs goals and supported bindings. (<a href=\"https:\/\/yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">yjs.dev<\/a>)<\/p>\n<p><a href=\"https:\/\/docs.yjs.dev\/\" target=\"_blank\" rel=\"noopener noreferrer\">Yjs Documentation<\/a> &#8211; API (<code>Y.Doc<\/code>, <code>Y.Text<\/code>), binary update format, editor bindings, GC\/compaction notes and persistence strategy. (<a href=\"https:\/\/docs.yjs.dev\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">docs.yjs.dev<\/a>)<\/p>\n<p><a href=\"https:\/\/automerge.org\/\" target=\"_blank\" rel=\"noopener noreferrer\">Automerge (official)<\/a> &#8211; Automerge project goals, JSON-like CRDT semantics, and cross-platform bindings. (<a href=\"https:\/\/automerge.org\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">automerge.org<\/a>)<\/p>\n<p><a href=\"https:\/\/share.github.io\/sharedb\/\" target=\"_blank\" rel=\"noopener noreferrer\">ShareDB Documentation (OT)<\/a> &#8211; ShareDB architecture, OT types (<code>json0<\/code>, <code>rich-text<\/code>), persistence adapters and pub\/sub for horizontal scaling. (<a href=\"https:\/\/share.github.io\/sharedb\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">share.github.io<\/a>)<\/p>\n<p><a href=\"https:\/\/josephg.com\/blog\/crdts-go-brrr\/\" target=\"_blank\" rel=\"noopener noreferrer\">CRDTs go brrr \u2014 Joseph Gentle (engineering blog)<\/a> &#8211; Practical benchmarking and engineering lessons comparing Yjs\/Automerge performance and memory behavior (real-world perspective). (<a href=\"https:\/\/josephg.com\/blog\/crdts-go-brrr\/?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">josephg.com<\/a>)<\/p>\n<p><a href=\"https:\/\/github.com\/yjs\/y-prosemirror\" target=\"_blank\" rel=\"noopener noreferrer\">y-prosemirror (Yjs binding for ProseMirror)<\/a> &#8211; Implementation and examples showing how Yjs integrates with ProseMirror for rich structured editing. (<a href=\"https:\/\/github.com\/yjs\/y-prosemirror?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">github.com<\/a>)<\/p>\n<p><a href=\"https:\/\/fluidframework.com\/docs\/v1\/faq\" target=\"_blank\" rel=\"noopener noreferrer\">Fluid Framework FAQ (Microsoft)<\/a> &#8211; Describes Fluid\u2019s approach (total order broadcast and DDSes), and clarifies that Fluid is not a pure OT or CRDT implementation. (<a href=\"https:\/\/fluidframework.com\/docs\/v1\/faq?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">fluidframework.com<\/a>)<\/p>\n<p><a href=\"https:\/\/ot.js.org\/docs\/operational-transformation\" target=\"_blank\" rel=\"noopener noreferrer\">OT.js \u2014 Operational Transformation docs<\/a> &#8211; Practical explanation and historical context for OT, including examples and links to implementations. (<a href=\"https:\/\/ot.js.org\/docs\/operational-transformation?utm_source=openai\" target=\"_blank\" rel=\"noopener noreferrer\">ot.js.org<\/a>)<\/p>\n<p>Apply the checklist, measure early, and let operational constraints \u2014 not theory preferences \u2014 decide whether OT or CRDT will fit your editor\u2019s product requirements.<\/p>\n<\/p><\/div>\n<\/div>\n<\/div>\n<\/div>\n<p>Fuente: <a href=\"https:\/\/dev.to\/beefedai\/crdt-vs-ot-choosing-the-right-collaboration-algorithm-50io\">Art\u00edculo original<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Fundamentals: How OT and CRDT actually work Trade-offs: Complexity, performance, storage, and latency Use cases: Which algorithm fits which problem Implementation considerations and popular libraries Migration paths and hybrid approaches Practical Application The choice between CRDT and OT defines your editor\u2019s user experience as much as your infrastructure: offline behavior, amount of metadata, and the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":2980,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[41],"tags":[],"class_list":["post-2981","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devto"],"jetpack_publicize_connections":[],"_links":{"self":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/2981","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/comments?post=2981"}],"version-history":[{"count":0,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/2981\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media\/2980"}],"wp:attachment":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media?parent=2981"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/categories?post=2981"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/tags?post=2981"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}