CRDT для синхронизации в мобильном приложении: Y.js и Automerge

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Услуги, которые мы предлагаем
Показано 1 из 1Все 1734 услуг
CRDT для синхронизации в мобильном приложении: Y.js и Automerge
Сложный
от 2 недель до 3 месяцев
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    858
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    744
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1161
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    1034
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    968
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    563

Конфликты при синхронизации — причина почти 70% ошибок в коллаборативных мобильных приложениях. Мы решили эту проблему для клиента из финтеха: 8 пользователей одновременно редактировали один документ, синхронизация через REST-ручки приводила к потерям данных. После внедрения Y.js количество инцидентов сократилось на 95%. Оценим ваш проект бесплатно за 24 часа. Наши инженеры — сертифицированные разработчики с 7+ годами опыта в офлайн-first архитектурах.

CRDT (Conflict-free Replicated Data Types) — математически гарантируют, что любые две реплики одного документа, получив одни и те же операции в любом порядке, придут к идентичному состоянию. Без координирующего сервера. Без разрешения конфликтов вручную. Это особенно ценно для мобильных приложений: пользователь редактирует в метро (офлайн), синхронизируется дома (онлайн), партнёр делал то же самое — merge происходит автоматически и детерминировано.

Что такое CRDT на практике

Это не один алгоритм, а семейство структур данных. Каждая решает свою задачу:

  • G-Counter — счётчик, который только растёт. Merge = max по каждому узлу.
  • LWW-Register (Last-Write-Wins) — одно значение, побеждает последнее по timestamp. Подходит для отдельных полей (название документа, статус).
  • OR-Set (Observed-Remove Set) — множество с add и remove. Решает проблему «удалил, а партнёр добавил одновременно» через уникальные теги для каждого add-операции.
  • RGA (Replicated Growable Array) — массив с insert/delete. Основа для текстового CRDT.
  • YATA (Yet Another Transformation Approach) — алгоритм Y.js, разновидность RGA.

Y.js: детальный разбор

Y.js — наиболее зрелая реализация CRDT для JavaScript/TypeScript. Использует YATA-алгоритм для YText и YArray, LWW для YMap.

Внутренняя структура YText: связный список элементов (Item), каждый с id: {client, clock}. client — уникальный clientID (uint32, генерируется при создании Y.Doc). clock — логические часы, монотонно растут для каждого клиента. Merge двух YDoc = объединение всех Item с детерминированным порядком при конфликтах (меньший clientID идёт первым при одинаковом логическом времени).

Ключевое: операция никогда не теряется. Даже если insert произошёл офлайн на одном устройстве, а другое устройство одновременно удалило текст вокруг — insert применится, может оказаться в «пустом» месте, но не потеряется.

Как работает синхронизация через Y.js?

Y.js — только алгоритм. Транспорт — отдельный провайдер:

Провайдер Транспорт Подходит для
y-websocket WebSocket Серверная синхронизация
y-webrtc WebRTC DataChannel P2P без сервера
y-indexeddb IndexedDB Локальная персистентность
y-leveldb LevelDB Серверное хранение

Для мобильного приложения: y-websocket для онлайн-синхронизации + кастомный провайдер для SQLite (персистентность на устройстве). Готового y-sqlite для React Native нет — реализуем через Y.encodeStateAsUpdate() и Y.applyUpdate() с сохранением в react-native-sqlite-storage.

// Сохранение в SQLite при каждом изменении
ydoc.on('update', (update, origin) => {
  if (origin !== 'sqlite') {  // не сохраняем изменения из SQLite
    const state = Y.encodeStateAsUpdate(ydoc);
    db.executeSql('INSERT OR REPLACE INTO docs (id, state) VALUES (?, ?)',
      [docId, Buffer.from(state).toString('base64')]);
  }
});

// Загрузка при открытии документа
const [result] = await db.executeSql('SELECT state FROM docs WHERE id = ?', [docId]);
if (result.rows.length > 0) {
  const state = Buffer.from(result.rows.item(0).state, 'base64');
  Y.applyUpdate(ydoc, new Uint8Array(state), 'sqlite');
}

Automerge: альтернатива Y.js

Automerge — CRDT-библиотека с другим подходом: документ — это JSON-объект с deep merge семантикой. Automerge 2.x переписан на Rust, скомпилирован в WASM — производительность на порядок выше первой версии.

Для React Native: @automerge/automerge работает через WASM в JSC/Hermes. На Hermes — нужно проверить поддержку WASM (в последних версиях RN Hermes поддерживает WASM, но не все билды).

Преимущество Automerge перед Y.js: схема данных — обычный JSON, не специальные типы. Но Y.js активнее поддерживается, больше провайдеров синхронизации. В тестах производительности Y.js быстрее Automerge в 2 раза при текстовых коллаборациях.

Как выбрать между Y.js и Automerge?

Критерий выбора — тип данных и платформа. Сравните ключевые характеристики:

Характеристика Y.js Automerge 2
Алгоритм YATA (для текста) RGA + JSON merge
Скорость (текст) ~2x быстрее Базовая
Типы данных YText, YArray, YMap JSON (автоматически)
Поддержка Flutter Через JS-биндинг FFI (WASM)
Размер сообщества Большое Среднее

Что такое векторные часы и как они помогают?

Y.js автоматически отслеживает stateVector — map из {clientId: maxClock}. При синхронизации двух реплик:

  1. Обмениваемся stateVector.
  2. Запрашиваем Y.encodeStateAsUpdateV2(ydoc, remoteStateVector) — дельта от того, что удалённая сторона ещё не знает.
  3. Применяем полученную дельту через Y.applyUpdateV2().

Это эффективная синхронизация без передачи всего документа. При переподключении после офлайна: отправляем свой stateVector, получаем только недостающие изменения.

Конвергентность: что гарантируют, чего нет

CRDT гарантирует Strong Eventual Consistency: если все реплики получили одни и те же операции — они сходятся к идентичному состоянию.

Не гарантируется семантическая корректность. Если пользователь A переименовал файл в "Report Q1", а пользователь B одновременно удалил этот файл — CRDT может восстановить файл с новым именем. Это математически правильно (add побеждает remove в OR-Set), но семантически может быть неожиданно для пользователя.

Решение: UX-слой, который показывает пользователю факт конфликта и его автоматическое разрешение. Не ломать работу, но дать информацию.

Производительность с большими документами

Y.js lazy-загружает структуру документа: части, которые не были запрошены, не декодируются. Для документов 1MB+ — важно. Y.Doc с gc: true (по умолчанию) автоматически удаляет tombstone-записи удалённых элементов, сжимая историю.

При большом количестве правок история операций разрастается. Y.encodeStateAsUpdate() содержит все изменения с момента создания. Компакция через Y.encodeStateAsUpdate(ydoc, emptyStateVector) — snapshot текущего состояния без истории. Для офлайн-приложений: хранить snapshot + delta после snapshot.

Что входит в работу по внедрению CRDT

  • Анализ требований и проектирование архитектуры синхронизации
  • Выбор протокола (Y.js, Automerge или кастомное решение)
  • Интеграция с локальным хранилищем (SQLite, PostgreSQL)
  • Реализация провайдера синхронизации (WebSocket, WebRTC)
  • Написание тестов на конфликты и производительность
  • Подготовка документации и обучение команды
  • Постпродакшн поддержка в течение месяца

Оценка

CRDT-синхронизация через Y.js для text/JSON документов в React Native — 6–10 недель (включая персистентность, reconnect-логику, conflict awareness UI). Для Flutter через Dart-биндингов к Y.js (через JS runtime) или нативного CRDT — 10–16 недель. Automerge 2 на Rust FFI для нативных платформ — 12–20 недель. Экономия на серверной инфраструктуре достигает 60% по сравнению с традиционными методами. Средний бюджет проекта составляет от 500 000 до 2 000 000 ₽ в зависимости от сложности. Свяжитесь с нами для бесплатной оценки — получите консультацию инженера в течение одного дня. Подробнее о спецификации CRDT читайте в Wikipedia и Y.js documentation.

Интеграция API в мобильное приложение: с чего начать

Запрос уходит, ответ не приходит, timeout — 30 секунд. Пользователь смотрит на спиннер. Сети нет — мобильная карта в метро. Или сеть есть, но сервер вернул 200 с HTML-страницей ошибки вместо JSON — и приложение крашит при JSONDecoder.decode(). Мы видим такие кейсы на каждом втором проекте. Поэтому интеграция API в мобильное приложение — это не просто вызов endpoint'а, а проектирование надёжного сетевого слоя: обработка ошибок, кэширование, offline-режим, certificate pinning. Закажите аудит текущего сетевого слоя — оценим проект за 1 день.

Почему стандартные библиотеки недостаточны? URLSession и OkHttp предоставляют базовый HTTP-клиент, но для production нужны retry с exponential backoff, валидация статус-кодов, типизированная десериализация и мониторинг состояния сети. Без этого приложение теряет данные и пользователей. Мы уже 5 лет занимаемся мобильной разработкой и реализовали более 30 проектов с интеграцией API на iOS, Android и Flutter — от стартапов до enterprise-решений.

Как выбрать протокол для интеграции API?

Протокол Размер ответа Скорость парсинга Кэширование Подходит для
REST большой (фиксированная структура) среднее HTTP-кеш + локальное CRUD, типовые экраны
GraphQL минимальный (только нужные поля) среднее (нормализованный кеш) in-memory кеш (Apollo) сложные UI с разными выборками
gRPC минимальный (protobuf) высокое на уровне стримов high-load, real-time, IoT
WebSocket — (бинарный/текст) вручную чаты, котировки, синхронизация

REST остаётся стандартом для большинства проектов. Но когда на экране профиля нужно 5 полей из 40, GraphQL исключает over-fetching и сокращает трафик на 30–60%. gRPC оправдан при тысячах запросов в минуту (trading, IoT) — бинарная сериализация в 3–5 раз быстрее JSON. WebSocket — единственный выбор для real-time без polling (сообщения, уведомления).

Пример из практики: для финтех-приложения мы заменили REST (40 полей) на GraphQL — размер ответа сократился с 12 КБ до 2,5 КБ, время рендера экрана упало на 70%. Экономия трафика составила около 15 000 ₽ в месяц при 100 000 активных пользователей.

Как обеспечить надёжность соединения и offline-first

Пользователи теряют сеть в метро, лифте, тоннеле. Мобильное приложение обязано работать без интернета — хотя бы в read-only режиме. Мы внедряем паттерн offline-first:

  1. При открытии экрана сначала показываем данные из локального кеша (Core Data / Room).
  2. Параллельно выполняем сетевой запрос, обновляем UI после ответа.
  3. Если сеть недоступна — показываем кешированные данные и метку «нет соединения».
  4. При восстановлении сети автоматически синхронизируем изменения.

Для кэширования HTTP-ответов используем URLCache (iOS) и OkHttp Cache (Android) с поддержкой Cache-Control. Для структурированных данных — SwiftData / Room. NWPathMonitor / ConnectivityManager.NetworkCallback отслеживают состояние сети и триггерят обновление.

REST и выбор клиентской библиотеки

Alamofire (iOS) — де-факто стандарт для Swift-проектов. Поверх URLSession добавляет request chaining, response validation, automatic retry, certificate pinning через ServerTrustManager. AF.request() с .validate() возвращает ошибку для любого статус-кода вне 200–299. Без .validate() Alamofire считает 404 и 500 успешными ответами. С Swift Concurrency — async-версия через serializingDecodable.

Retrofit (Android) — аннотационный HTTP-клиент поверх OkHttp. Интерфейс с аннотациями компилируется в реализацию. @GET, @POST, @Path, @Query, @Body — декларативное описание API. OkHttp под капотом: connection pooling, transparent gzip, HTTP/2 multiplex. HttpLoggingInterceptor — логирование в debug-сборке. Authenticator — автоматический refresh токена при 401.

Ktor (KMM/Flutter) — мультиплатформенный HTTP-клиент. На iOS работает через Darwin engine (URLSession), на Android — через OkHttp. Единый код для обеих платформ при KMM-архитектуре.

GraphQL: когда REST не справляется

REST возвращает фиксированную структуру. Экран профиля требует name, avatar, email — сервер отдаёт 40 полей. Over-fetching. GraphQL решает это: клиент запрашивает ровно нужные поля. Это критично для мобайла, где трафик и время парсинга — реальные ограничения. Apollo iOS и Apollo Kotlin генерируют типизированные классы по схеме: schema.graphql + query-файлы → строгие типы на этапе компиляции. Subscriptions через WebSocket — real-time без polling. Ограничение: GraphQL сложнее кешировать на уровне HTTP. Apollo использует нормализованный in-memory кеш InMemoryNormalizedCache — запросы с пересекающимися данными обновляют кеш без дублирования. Apollo GraphQL Documentation

WebSocket: real-time без лишнего трафика

Polling (setInterval каждые 5 секунд) — трата батареи и трафика. WebSocket — постоянное двунаправленное соединение. iOS: URLSessionWebSocketTask (нативный, iOS 13+). Android: OkHttp WebSocket. Обязательная обработка reconnect: при onFailure — экспоненциальный backoff (1с → 2с → 4с → 8с → максимум 60с). Socket.IO — надстройка с автоматическим reconnect, но для новых проектов предпочтительнее нативный WebSocket (меньше зависимостей).

gRPC: для высоконагруженных сервисов

gRPC с protobuf — бинарная сериализация: меньше размер, быстрее парсинг. grpc-swift для iOS, grpc-kotlin для Android. Protobuf-схема компилируется в типизированные классы. Streaming (server-side, client-side, bidirectional) — нативная возможность. Порог применения: высокая частота запросов (trading, IoT) или критичная latency. Для обычного CRUD REST проще в дебаге и мониторинге.

Certificate Pinning и безопасность

Корпоративный proxy может перехватить HTTPS через подмену сертификата. Certificate pinning предотвращает это: приложение принимает только конкретный сертификат или публичный ключ. Alamofire: ServerTrustManager с PinnedCertificatesTrustEvaluator. OkHttp: CertificatePinner с SHA-256 хешем. Операционная сложность: при ротации сертификата старые версии приложения перестают работать. Решение — pinning на публичный ключ CA или поддержка нескольких пинов с grace period. Подробнее о certificate pinning на Wikipedia

Что входит в работу

Этап Длительность Результат
Анализ API и requirements 1–2 дня Спецификация эндпоинтов, выбор протокола, схема кэширования
Реализация сетевого слоя 3–5 дней Клиентская библиотека, обработка ошибок, retry, pinning
Offline-режим и кеширование 2–3 дня Локальное хранилище, offline-first паттерн
Интеграция и тестирование 2–3 дня Юнит-тесты (URLProtocol/OkHttp MockWebServer), UI-тесты
Деплой и документация 1 день CI/CD, доступы к сторам, README для команды

Мы передаём: исходный код сетевого слоя, документацию по используемым библиотекам, инструкцию по ротации сертификатов, поддержку в течение 2 недель после сдачи.

Сроки и стоимость

Реализация сетевого слоя с REST, retry, кэшированием и offline-режимом — 1–2 недели. Добавление GraphQL или WebSocket — ещё 1–2 недели. gRPC — 2–3 недели, включая кодогенерацию. Стоимость рассчитывается индивидуально после анализа API и требований к offline-поведению. Оценим проект за 1 день — свяжитесь для консультации.