Конфлікт-резолюція синхронізації даних у мобільних додатках

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

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

Це лише деякі з типів мобільних додатків, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

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

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

Часті запитання

Останні роботи

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    894
  • 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-ядро, порти для 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+ років досвіду в мобільній розробці та гарантують цілісність даних. Для оцінки вартості та термінів отримайте безкоштовну консультацію.