Курсоры — самая простая часть
Когда два человека двигают прямоугольники в одном файле, кажется, что достаточно отправлять координаты по WebSocket. Настоящая работа начинается при обрыве сети: оба клиента продолжают редактировать, объекты создаются и удаляются, слои меняют родителей, а после подключения документ должен сойтись в одно состояние.
Figma изучила Operational Transformation и CRDT, но не стала переносить академическую модель целиком. Причина хорошая: Figma — централизованный сервис и редактор объектов, а не децентрализованный текстовый документ.
Сначала сузить задачу
Документ Figma можно представить как карту объектов, у каждого — карта свойств. Сервер хранит последнее полученное значение конкретного свойства конкретного объекта. Если два человека меняют разные свойства, конфликта нет. Если одно и то же — побеждает значение, которое сервер получил последним.
Для текста такой компромисс грубоват: одновременные правки строки не обязаны красиво склеиться. Для дизайна конфликт на одном свойстве одного слоя редок, а простота алгоритма даёт выигрыш в скорости, тестировании и отладке.
Интерфейс не ждёт разрешения
Клиент применяет локальное изменение мгновенно. Ждать круг до сервера означало бы сделать каждый drag вязким. Пока подтверждение не пришло, клиент считает свою правку лучшим прогнозом будущего состояния и не даёт более старым сообщениям сервера вызвать мерцание.
Это полезный шаблон для корзины, доски задач и редактора: быстрый optimistic UI плюс явный протокол подтверждения. Но у каждого действия должен быть сценарий отказа. Если сервер отклонил операцию, пользователь не должен молча потерять работу.
Дерево сложнее карты
Слои образуют дерево. При одновременном переподчинении можно случайно получить цикл: A становится ребёнком B, а B — ребёнком A. Сервер Figma отклоняет связь, создающую цикл. Клиент временно может показать редкое несовершенное состояние, пока не получит решение сервера. Команда сознательно приняла этот небольшой визуальный дефект вместо большого усложнения протокола.
Порядок соседних слоёв хранится через fractional indexing: позиция — значение между соседями. Чтобы вставить объект, можно взять середину между двумя индексами, не перенумеровывая весь список. Важно, что родитель и позиция обновляются атомарно.
Надёжность достраивали после ценности
Поначалу состояние активного файла находилось в памяти процесса и периодически сохранялось целиком. Позже Figma добавила write-ahead log: операции сначала фиксируются в надёжном журнале, а затем применяются. Компания сообщала, что 95% правок сохранялись в пределах 600 мс.
Последовательность показательна. Команда сначала доказала, что multiplayer нужен людям, и только потом перестроила фундамент под масштаб. Это не призыв пренебрегать данными; это способ не оплачивать будущую сложность до появления будущего.
Что забрать в свой продукт
Опишите единицу конфликта: символ, поле, карточка, объект. Решите, нужен ли настоящий peer-to-peer или сервер может быть судьёй. Создайте маленький симулятор из трёх клиентов, добавьте задержку, офлайн и перестановку сообщений. Такой стенд быстро покажет то, что счастливый сценарий скрывает месяцами.
Хорошая real-time архитектура не та, где нет конфликтов. Хорошая — где редкие конфликты локальны, предсказуемы и не заставляют пользователя думать о распределённых системах.