Skip to content
PПромтбук
RUEN
04Архитектура

Real-time collaboration: CRDT vs OT vs lock-based

Выбор стратегии для collaborative editing (как Figma/Notion/Linear): CRDT vs Operational Transform vs lock-based. Trade-offs, реализация, edge cases.

Спроектируй стратегию real-time collaboration для редактора. Контент: {{content_type}}. Пользователей одновременно: {{user_count}}. Offline: {{offline_required}}.

Действуй как architect, который уже пару раз обжёгся на наивных подходах. Не бросайся в CRDT "потому что модно" — каждый из трёх подходов выигрывает в своём контексте.

1. Когда какой подход (decision tree)

Lock-based (один редактирует, остальные смотрят):

  • Контент = единое целое, мердж невозможен (бинарь, изображение целиком, видеомонтаж проект)
  • Пользователей мало (2-5), осознанные хэндоверы
  • Прецедент: старый Google Docs до 2010, многие CAD-системы

Operational Transform (OT):

  • Центральный сервер обязателен (sequential broadcast)
  • Линейный текст / структура с чётким total order операций
  • Прецедент: Google Docs, Etherpad, ShareDB
  • НЕ для offline-first

CRDT:

  • Нужен offline + автомерж без сервера-арбитра
  • P2P или federated сценарии
  • Структура легко представима как state-based / op-based CRDT (text, list, map, counter)
  • Прецедент: Figma (custom CRDT для графики), Linear (sync engine), Automerge, Y.js

Если вопрос "у меня форма с 5 полями" — НЕ нужна collaboration вообще. Last-write-wins + presence хватит.

2. Сравнительная таблица

СвойствоLock-basedOTCRDT
Consistencystrong (one writer)eventual via serverstrong eventual (CAP-friendly)
Offlinetrivial (single writer)нет (нужен server roundtrip)yes (по сути сильная сторона)
Complexityнизкаяочень высокая (transform matrix)средняя для готовых libs, высокая custom
Size overheadнольноль (только ops)metadata: 30-300% от контента
Serveroptionalобязателен и центральныйoptional (можно P2P)
Conflict UX"user X is editing"прозрачно mergeпрозрачно, но возможны сюрпризы
LatencyN/Aserver roundtripoptimistic local, sync async

3. Implementation patterns

Y.js (рекомендация для текст/document):

  • Готовый CRDT, есть bindings для ProseMirror / Quill / CodeMirror / Monaco
  • y-websocket / y-webrtc для транспорта
  • Awareness API для presence (cursors, selections, online users)
  • Pros: production-ready, маленький overhead, отличная подгонка под текст
  • Cons: своих структур не добавишь без знания внутренностей

Automerge:

  • JSON-like CRDT, удобно для произвольных структур (settings, lists, maps)
  • Хорошая history, time-travel
  • Pros: декларативно, "просто меняешь объект"
  • Cons: тяжелее Y.js, история растёт без compaction

ShareDB (OT):

  • Если ты Google Docs-like и server-centric
  • JSON OT, рабочая модель
  • Минусы: нужен mongoDB или совместимый persistence, не для offline

Custom CRDT (Figma-style):

  • Когда домен специфичный: графика, координаты, z-order, групповые transform
  • Pros: оптимально для домена, маленький overhead
  • Cons: месяцы R&D, ловушки на каждом шагу

Правило: не пиши свой CRDT, если только нет специфичного домена с измеримой экономией.

4. Edge cases (вот где ломается)

  • Conflict resolution с семантикой: два пользователя поменяли цену в инвойсе с 100 на 90 и 110. CRDT возьмёт последний по timestamp — но возможно нужен явный prompt "у вас конфликт"
  • Undo с multi-user: undo твоего действия — это НЕ undo последней операции на сервере. Нужен per-user undo stack, который умеет "отменить только мои"
  • Cursor / selection presence: при удалении текста чужой курсор не должен оказаться "в воздухе" — нужен sticky position (привязка к relative pos, не absolute index)
  • Offline rebase: ушёл оффлайн на 2 часа, вернулся — 200 чужих операций. Применяешь свои поверх? Нужен merge с возможностью review больших расхождений
  • Server crash mid-op: OT — потеря порядка = corruption. CRDT — переживёт, но клиенты могут разойтись по версиям до восстановления
  • Large history: документу год, 50К операций. Нужен compaction (snapshot + clear ops до X), иначе load = боль
  • Schema evolution: добавил поле в block-структуру. Старые клиенты с CRDT-стейтом без поля — что произойдёт при merge?
  • Permissions mid-edit: пользователь потерял доступ к разделу пока его редактировал — откатываем его незакоммиченное?

5. Output format

## Chosen approach
**Подход:** <CRDT (Y.js) / OT (ShareDB) / Lock-based / Hybrid>
**Rationale:**
- <почему этот, исходя из {{content_type}}, {{user_count}}, {{offline_required}}>
- <что отвергли и почему>

## Architecture
- Transport: <WebSocket / WebRTC / SSE>
- Server role: <relay / authority / none>
- Persistence: <снапшот частота, op log retention>
- Presence: <как доставляем cursor/avatar/status>

## First-steps roadmap (по неделям)
1. PoC: один документ, два клиента, базовый merge  1 неделя
2. Presence + cursors  1 неделя
3. Persistence + reconnect/replay  2 недели
4. Undo per-user  1-2 недели
5. Conflict UX (где автомерж нежелателен)  1 неделя
6. Permissions integration, audit  1 неделя
7. Load test (целевой {{user_count}}), compaction  2 недели

## Risks
- <топ-3 риска, как митигируем>

Anti-patterns

  • ❌ CRDT для всего → size overhead, batch updates трудно, история раздувается
  • ❌ OT без полной operational transform matrix → бесконечные edge cases, "иногда буквы пропадают"
  • ❌ Lock-based для творческой работы → "you can't edit, user X is editing" убивает flow
  • ❌ Без presence (cursor/avatar/typing) → users думают что одни, дублируют работу
  • ❌ Без conflict UX → автомерж может молча стереть осмысленные изменения
  • ❌ Sync через REST polling каждую секунду → battery drain, latency, бессмысленный traffic
  • ❌ Свой CRDT "за выходные" → через год всё ещё ловишь edge cases, которые Y.js решил
  • ❌ Игнорировать compaction → через месяц load документа = 5 секунд
К подразделу «Архитектура»
Похожие промты