CRDTs vs OT is a solved question in 2026. Where you draw the sync boundary is not.
Row replication, document CRDTs, and event logs solve local-first sync differently, and each breaks migrations or multi-device identity its own way.
Local-first software stopped being a research topic somewhere around 2024 and turned into infrastructure. Yjs is now the most widely deployed CRDT library in the world, running inside Tiptap, BlockSuite and Hocuspocus. Automerge has its own production users where rich-text merge quality matters more than raw throughput. Figma's multiplayer cursors, Linear's offline issue sync, Apple Notes and Obsidian Sync all rest on the same family of data structures. The CRDT versus operational transformation argument that used to fill conference talks is, in practice, over.
What isn't settled is the question underneath it: which part of an application should actually be the thing that syncs. Martin Kleppmann framed this well on a Software Engineering Radio episode earlier in 2026, and it's the right way to think about local-first sync now: not CRDT or OT, but which sync engine boundary, replicate Postgres rows, replicate document operations, or replicate an event log, and what that choice does to schema migrations and multi-device identity six months later.
Three ways to draw the sync boundary
Every local-first architecture ends up choosing one of three boundaries, whether or not the team picking it realises there were three options.
- Row-level replication syncs individual database rows, typically out of Postgres via logical replication, to a local store on the client. Conflicts get resolved per row or per field.
- Document CRDTs sync a single structured document, most often rich text or a design canvas, and resolve conflicts inside that structure using algorithms like Peritext.
- Event logs sync a append-only sequence of domain events, and every client rebuilds its own state by replaying that sequence.
Each boundary is a genuinely good choice for the right feature, and a genuinely bad one for the wrong feature. The mistake teams make in 2026 isn't picking CRDTs over OT, that decision barely matters anymore. It's picking a boundary that fits the demo but not the feature.
Row-level replication: the easiest start, the hardest migration
Tools like ElectricSQL, PowerSync and Zero have made row-level sync the path of least resistance for teams already running Postgres. ElectricSQL streams read-path changes out via Shapes, filtered subsets of tables, and leaves writes to an existing backend API. PowerSync goes further, syncing bidirectionally with an upload queue that lets a mobile client write while offline and reconcile later. Zero, built by the team behind Replicache, optimises for instant, optimistic UI updates against whatever backend already exists.
The appeal is obvious: the data model is already a relational schema, so there's no new mental model to learn. The cost shows up the first time that schema changes. A column rename or a foreign key change now has to work correctly against every client still holding an older shape of that row, possibly offline, possibly for days. A migration that used to be a single `ALTER TABLE` statement run once against one database becomes a distributed rollout, with old and new schema shapes coexisting across a fleet of devices that don't all check in at the same time.
Document CRDTs: rich merges, harder identity
Where the feature really is a shared document, collaborative text, a whiteboard, a design canvas, document CRDTs are the right layer, and Yjs or Automerge will do most of the hard work. What they don't solve for free is multi-device identity: a CRDT needs a stable, unique actor ID per writer to merge correctly, and a user who edits from a laptop, a phone and a tablet is, from the CRDT's point of view, three different actors unless the application explicitly reconciles them.
The other cost is storage growth. CRDTs typically keep tombstones, markers for deleted content, so that a delete from one device merges correctly with a concurrent edit from another. Left alone, tombstones accumulate indefinitely, and a document that's been edited for two years can carry far more deleted history than live content. Production Yjs and Automerge deployments need an explicit garbage-collection or snapshotting strategy; it is not something either library does automatically once concurrent edit history is no longer needed.
Event logs: the cleanest history, the most work per client
Syncing an append-only event log, rather than rows or documents, gives an application the cleanest possible audit trail: every state change is a durable, ordered fact, and any client can reconstruct any past state by replaying from the start. That's why it's the boundary of choice for anything that needs a real audit history, financial ledgers, compliance-sensitive workflows, systems where undo needs to go back further than one step.
The cost lands on every client rather than on the server. A client that has been offline for a month has to replay a month of events before it has a usable state, and ordering events correctly across devices that were offline at different times, and might have generated conflicting events, is a harder problem than either of the other two boundaries has to solve. Event logs also tend to grow without bound unless a team builds snapshotting into the sync protocol from day one, not after the log has already become the bottleneck.
| Boundary | Good fit | What breaks later |
|---|---|---|
| Row-level (ElectricSQL, PowerSync, Zero) | Offline CRUD apps already on Postgres | Schema migrations become a distributed rollout |
| Document CRDT (Yjs, Automerge) | Collaborative text, whiteboards, design canvases | Multi-device identity and unbounded tombstone growth |
| Event log | Audit trails, financial ledgers, deep undo | Replay cost and cross-device event ordering |
Picking a boundary for the feature you actually have
The decision gets easier once it's framed around the feature rather than the technology. A team building an offline-capable CRUD app, a field-service tool, an inventory tracker, is almost always better served starting with row-level sync against Postgres, because the schema already exists and the team already understands it. A team building a genuinely collaborative editing surface, where two people type in the same paragraph at the same time, should reach for a document CRDT; trying to model that with row-level sync means fighting the tool the whole way. A team that needs a real, replayable history of every change, for compliance or for a feature like unlimited undo, should treat the event log as the source of truth and derive everything else from it.
“The library was never the hard part. The boundary is.”
What to plan for regardless of which boundary you pick
Three things apply no matter which of the three a team chooses, and all three are easier to build in from the start than to retrofit:
- A schema or document versioning strategy that assumes old and new shapes will coexist across a fleet of clients for longer than expected, not just during a deploy window.
- An explicit multi-device identity model, so the same human editing from two devices is reconciled deliberately rather than treated as two unrelated actors.
- A garbage-collection or snapshotting budget, decided before tombstones or event logs grow large enough to make sync slow, not after.
None of this shows up in a demo. A local-first prototype with two browser tabs editing the same document looks identical whether the underlying boundary is rows, a document CRDT, or an event log. The difference only becomes visible months later, in the migration that has to roll out to a device that's been offline since before the schema changed, or the merge that quietly assumed one writer per user. Picking the boundary early, on purpose, is what keeps that difference from becoming an incident.
Frequently asked questions
Related reading
Railway disconnected a carrier to contain an outage. It cut its last route instead.
A July 2 Railway outage report shows the real damage came after the fix: a containment decision that removed the last default route, and a well-known Linux default that turned a fallback path into a silent bottleneck.
pgvector's HNSW index has a memory cliff, and the Postgres defaults walk right into it
pgvector handles most RAG workloads under ten million vectors just fine. The HNSW index underneath it has a memory requirement Postgres won't mention until the build already ran 40x slower.
X, Zoom, and Teams went down from one fibre cut. The transit layer doesn’t show up on most redundancy diagrams.
A severed Zayo fibre route took down X, Zoom, Reddit, and Teams within minutes. Anycast and multi-region failover were never the layer protecting against this.