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-based | OT | CRDT |
|---|---|---|---|
| Consistency | strong (one writer) | eventual via server | strong eventual (CAP-friendly) |
| Offline | trivial (single writer) | нет (нужен server roundtrip) | yes (по сути сильная сторона) |
| Complexity | низкая | очень высокая (transform matrix) | средняя для готовых libs, высокая custom |
| Size overhead | ноль | ноль (только ops) | metadata: 30-300% от контента |
| Server | optional | обязателен и центральный | optional (можно P2P) |
| Conflict UX | "user X is editing" | прозрачно merge | прозрачно, но возможны сюрпризы |
| Latency | N/A | server roundtrip | optimistic 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 секунд
Multi-agent: координатор и специалисты
Архитектура из координатора и специализированных агентов: передача контекста, дедупликация, race conditions.
Новый subagent или новый skill: что выбрать
Decision tree: создавать ли отдельного агента или достаточно skill. Критерии — контекст, переиспользование, frequency, complexity.
Architecture Decision Record (ADR)
Зафиксировать архитектурное решение: контекст, варианты, выбор и trade-offs.