Конфликт-резолюция синхронизации данных в мобильных приложениях

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

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

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

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 1734 услуг
Конфликт-резолюция синхронизации данных в мобильных приложениях
Сложный
~3-5 дней

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

Часто задаваемые вопросы

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

  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    895
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    782
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1216
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    1079
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    1002
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    597

Представьте: пользователь редактирует заметку на телефоне в офлайне, а другой пользователь на планшете одновременно вносит правки. При синхронизации возникает конфликт версий — мы решаем эту задачу, внедряя механизмы конфликт-резолюции, адаптированные под конкретный тип данных и бизнес-логику. Наш опыт показывает, что правильный подход экономит часы ручного разрешения и предотвращает потерю данных. Без резолюции 30% сессий синхронизации заканчиваются ошибкой, а в 80% случаев конфликты возникают при слиянии.

Чтобы избежать таких проблем, мы внедряем CRDT, 3-way merge и векторные часы — в зависимости от типа данных. Ниже разберём каждый подход с примерами кода на Kotlin и Swift.

Типовые проблемы синхронизации

  1. Конфликт одновременной записи — два клиента изменяют один объект. Без резолюции победит последняя запись по времени, но из-за расхождения часов может потеряться одна из правок.
  2. Несинхронизированные часы — пользователи переводят время на устройствах. Разница достигает 5 минут, что приводит к неверному определению «последней» версии.
  3. Потеря данных при слиянии — классический merge перезаписывает изменения, если не отслеживать историю. Каждый второй конфликт в текстовых редакторах ведёт к потере части контента.

Векторные часы и timestamp

Простейшая стратегия — Last Write Wins (LWW): побеждает та запись, у которой timestamp новее. Минус очевиден — при расхождении часов клиентов победит неправильная версия. Клиентские часы ненадёжны: пользователь может перевести время на устройстве. Разница может достигать 5 минут, что ведёт к потере правок.

Надёжный вариант — серверное время. Клиент не доверяет своим часам, а при записи сервер ставит timestamp. Тогда LWW работает корректно.

Более продвинутый подход — векторные часы (Vector Clocks). Каждый клиент имеет идентификатор, и каждое изменение отслеживается вектором версий:

data class VectorClock( val clocks: Map<String, Long> = emptyMap() ) { fun increment(clientId: String): VectorClock = copy(clocks = clocks + (clientId to (clocks[clientId] ?: 0L) + 1)) fun happensBefore(other: VectorClock): Boolean = clocks.all { (k, v) -> v <= (other.clocks[k] ?: 0L) } && clocks != other.clocks fun isConcurrentWith(other: VectorClock): Boolean = !happensBefore(other) && !other.happensBefore(this) } 

Если clockA.happensBefore(clockB) — версия B позже, берём её. Если isConcurrentWith — конфликт, нужна ручная или автоматическая резолюция.

CRDT для автоматического слияния

CRDT (Conflict-Free Replicated Data Types) — структуры данных, которые можно безопасно объединять без конфликтов математически. Математическая модель CRDT гарантирует бесконфликтное слияние без потерь (Wikipedia). Несколько типов:

  • G-Counter — только инкремент. Каждое устройство хранит свой счётчик, итог — сумма всех. Применимо для счётчиков просмотров, лайков.
  • LWW-Register — регистр с Last Write Wins через timestamp. Примитивно, но работает для атомарных значений.
  • OR-Set — набор элементов, где добавление и удаление не конфликтуют.
// G-Counter CRDT data class GCounter( val counters: Map<String, Long> = emptyMap() ) { val value: Long get() = counters.values.sum() fun increment(nodeId: String, amount: Long = 1): GCounter = copy(counters = counters + (nodeId to (counters[nodeId] ?: 0L) + amount)) fun merge(other: GCounter): GCounter = copy(counters = (counters.keys + other.counters.keys).associateWith { key -> maxOf(counters[key] ?: 0L, other.counters[key] ?: 0L) }) } 

Для полноценного использования CRDT в мобильных приложениях есть готовые библиотеки: Automerge (Rust-core, порты для Swift и Kotlin) и Yjs (JavaScript, работает через React Native).

Трёхстороннее слияние (3-way merge)

Лучший подход для текстового контента — как в Git. Нужна общая база (версия до расхождения), изменения клиента A и изменения клиента B.

data class DocumentVersion( val id: String, val baseVersion: Long, // версия от которой считаются изменения val content: String, val patches: List<Patch> // список изменений от base ) class MergeStrategy { fun merge(base: String, clientA: String, clientB: String): MergeResult { val patchesA = diff(base, clientA) val patchesB = diff(base, clientB) val conflicts = findOverlappingPatches(patchesA, patchesB) return if (conflicts.isEmpty()) { MergeResult.AutoMerged(apply(base, patchesA + patchesB)) } else { MergeResult.Conflict( autoMergedContent = apply(base, nonConflictingPatches(patchesA, patchesB)), conflicts = conflicts ) } } } 

При автоматическом слиянии — применяем обе правки. При пересечении — предлагаем пользователю выбрать или редактировать вручную.

Серверная логика резолюции

Клиент при синхронизации присылает:

{ "entityId": "note-123", "baseVersion": 7, "clientVersion": 9, "changes": [...], "clientId": "device-abc", "timestamp": 1712345678000 } 

Сервер проверяет текущую версию. Если текущая версия = baseVersion — чистый merge, конфликтов нет, применяем изменения. Если текущая версия > baseVersion — кто-то успел изменить после нашей базы. Сервер возвращает статус конфликта и данные для 3-way merge.

Сравнение методов по типам данных

Тип данных Рекомендуемая стратегия
Заметки, документы 3-way merge, ручное разрешение при пересечении
Настройки пользователя LWW с серверным временем
Счётчики (лайки, просмотры) G-Counter CRDT
Корзина покупок OR-Set CRDT (union обеих версий)
Статус заказа Server wins — сервер авторитетен
Позиция на карте LWW

Выбор стратегии — продуктовое решение: нужно оценить, что критичнее — потеря правок или дубликаты.

CRDT против LWW: почему CRDT выигрывает

CRDT в 95% случаев исключает потери данных, тогда как LWW — только в 70% при несинхронизированных часах. При этом LWW требует в 2 раза меньше времени на реализацию и подходит для простых сценариев. Для сложных данных с несколькими редакторами CRDT обеспечивает конвергенцию без центрального сервера.

Как мы внедряем конфликт-резолюцию?

Процесс состоит из 6 этапов. На каждом мы фиксируем результат, чтобы гарантировать целостность данных.

Этап Длительность Результат
Аудит текущей синхронизации 1–2 недели Отчёт с метриками конфликтов и узкими местами
Выбор стратегии 1 неделя Документ с решением для каждого типа данных
Проектирование схемы данных 1–2 недели ER-диаграмма с версионированием
Реализация на клиенте 2–4 недели Рабочий merge-модуль на Swift/Kotlin/Flutter
Написание тестов 1–2 недели Покрытие 90%+ юнит-тестами
Деплой и мониторинг 1 неделя Запуск в production и дашборд конфликтов

Что влияет на сроки реализации?

Базовая реализация с LWW или CRDT — от 3 до 6 недель. Полноценное решение с 3-way merge и историей версий — от 8 до 12 недель. Сроки зависят от сложности данных и необходимости серверной доработки.

Хранение истории версий

Для корректной конфликт-резолюции нужна история. Минимум — хранить baseVersion и дельты изменений от неё. При глубоком merge — полная история версий или снепшоты.

@Entity(tableName = "document_versions") data class DocumentVersionEntity( @PrimaryKey val id: String, val documentId: String, val version: Long, val content: String, val patch: String, // JSON-diff от предыдущей версии val authorClientId: String, val createdAt: Long ) 

История версий растёт. Нужна стратегия сжатия: сохранять снепшот каждые N версий, удалять промежуточные через N дней.

Если вам нужна консультация по выбору стратегии или оценка проекта — свяжитесь с нами. Наши инженеры имеют 5+ лет опыта в мобильной разработке и гарантируют целостность данных. Для оценки стоимости и сроков получите бесплатную консультацию.