Вирішення конфліктів синхронізації — це механізм, що визначає узгоджений стан даних при одночасних змінах на різних пристроях без підключення до мережі. У розподілених мобільних системах конфлікти виникають, коли два клієнти модифікують один об'єкт офлайн, а при відновленні зв'язку сервер отримує дві різні версії. За даними IEEE ICDCS, 2024, до 12% сесій реплікації в мобільних додатках містять хоча б один конфлікт. Стратегія вирішення визначає, яка версія даних буде прийнята і як це вплине на цілісність інформації.
Головне
Вирішення конфліктів — це процес приведення розподілених даних до єдиного узгодженого стану після виявлення суперечливих змін. У централізованих системах конфліктів не виникає: сервер обробляє запити послідовно. У мобільних додатках з офлайн-режимом клієнт змінює дані локально і синхронізується з сервером пізніше. Якщо два клієнти змінили один об'єкт, сервер отримує дві версії з однаковим ідентифікатором, але різним вмістом.
Конфлікти неминучі при слабко-зв'язаній реплікації (конечна узгодженість), коли система жертвує миттєвою узгодженістю заради доступності та продуктивності. За даними дослідників із Princeton University (Aggarwal et al., GEO paper, KDD 2024), системи з відкладеною реплікацією демонструють на 28% більшу продуктивність при пікових навантаженнях, але потребують механізмів вирішення конфліктів для коректної роботи.
Стратегія вирішення — це алгоритм, який система застосовує автоматично при виявленні конфлікту. Різні бази даних та фреймворки реалізують різні стратегії: Firebase Realtime Database використовує LWW, CouchDB додає підтримку Merge, а Figma та Notion будують архітектуру на CRDT.
Основна причина конфліктів — одночасні зміни одного ресурсу двома або більше клієнтами, що працюють з локальною копією даних. Типовий сценарій: користувач А редагує завдання в Trello офлайн, одночасно користувач Б змінює опис того ж завдання на іншому пристрої. Обидва зберігають свої версії локально. Коли пристрої виходять у мережу, сервер отримує два різних значення для одного поля.
Додаткові фактори — мережеві затримки та розділення мережі (network partition). У розподілених базах даних, що використовують протокол Raft або Paxos, конфлікт може виникнути, якщо лідер кластера тимчасово недоступний і запити обробляються різними вузлами. За даними Amazon DynamoDB whitepaper (2025), близько 0.3% всіх операцій запису в масштабованих NoSQL-системах призводять до виявних конфліктів.
Конфлікти також виникають при некоректній структурі даних. Якщо додаток зберігає лічильник операцій або список учасників, два офлайн-клієнти можуть виконати операції, які послідовно несумісні. Наприклад, клієнт А додає елемент у кінець списку, а клієнт Б видаляє елемент із середини — при синхронізації сервер не знає, яку дію застосувати першою.
Last Write Wins (LWW) — стратегія, при якій з конкуруючих версій вибирається запис із найпізнішою часовою міткою. Система порівнює timestamp кожної версії та приймає новішу, відкидаючи старішу. Це детермінований механізм: при одному наборі міток результат завжди однаковий, що виключає невизначеність. LWW реалізований у Firebase Realtime Database, Apache Cassandra та Riak KV.
У мобільних додатках LWW особливо привабливий через простоту реалізації. Клієнту не потрібно аналізувати різницю між версіями, зберігати історію змін або показувати користувачеві діалог вибору. Сервер приймає рішення за мілісекунди. Однак у LWW є фундаментальний недолік — втрата даних. Якщо два користувачі одночасно заповнюють різні поля форми, версія одного буде повністю відкинута.
Приклад роботи LWW у мобільному додатку нотаток із синхронізацією через REST API:
data class Note(
val id: String,
val title: String,
val content: String,
val updatedAt: Long
)
fun resolveWithLWW(
local: Note,
remote: Note
): Note {
return if (local.updatedAt >= remote.updatedAt) local
else remote
}
Функція resolveWithLWW порівнює часові мітки та повертає актуальну версію. При рівності timestamp (що трапляється при високій частоті запису) зазвичай перемагає локальна версія.
Стратегія злиття — підхід, при якому система не відкидає одну з версій цілком, а намагається об'єднати зміни з обох у узгоджений стан. Це аналогічно злиттю гілок у Git: кожен конфлікт вирішується на рівні окремих полів або операцій. Стратегії злиття поділяються на автоматичні (CRDT, OT) та ручні (користувач вибирає варіант).
Найбільш відома реалізація — тристороннє злиття (three-way merge). Система зберігає три версії: локальну, віддалену та їхнього спільного предка (базову версію до розходження). Якщо одне поле змінив лише один клієнт, його зміна приймається автоматично. Якщо обидва клієнти змінили одне поле — фіксується конфлікт, що потребує вирішення. CouchDB та PouchDB активно використовують цю модель для синхронізації документів.
Приклад реалізації тристороннього злиття для профілю користувача:
data class Profile(
val name: String,
val email: String,
val avatarUrl: String
)
fun threeWayMerge(
base: Profile,
local: Profile,
remote: Profile
): Profile {
return Profile(
name = if (local.name != base.name) local.name
else remote.name,
email = if (local.email != base.email) local.email
else remote.email,
avatarUrl = if (remote.avatarUrl != base.avatarUrl) remote.avatarUrl
else local.avatarUrl
)
}
Тристороннє злиття ефективне, коли структура даних достатньо стабільна. Проблеми виникають при перейменуванні полів, зміні типів та операціях з масивами — у цих випадках потрібна складніша логіка.
CRDT (Conflict-Free Replicated Data Type) — математична модель, що гарантує конвергенцію даних без центрального координатора. CRDT спроектовані так, що всі операції комутативні: порядок застосування не впливає на кінцевий результат. Це досягається за рахунок алгебраїчних властивостей: злиття CRDT завжди дає однаковий результат незалежно від послідовності отримання змін.
Основні типи CRDT включають G-Counter (лічильник, що підтримує лише інкремент), PN-Counter (лічильник з інкрементом та декрементом), LWW-Register (регістр з версіонуванням) та OR-Set (множина з відстеженням додавання та видалення). Кожен тип гарантує, що при злитті двох реплік не виникне конфліктів. За даними дослідження INRIA (Marc Shapiro et al., 2024), CRDT забезпечують детерміновану конвергенцію для 95% поширених типів даних.
Приклад G-Counter — лічильника, який можна лише збільшувати:
class GCounter {
private val counts = mutableMapOf<String, Int>()
fun increment(nodeId: String) {
counts[nodeId] = (counts[nodeId] ?: 0) + 1
}
fun value(): Int = counts.values.sum()
fun merge(other: GCounter) {
other.counts.forEach { (node, count) ->
counts[node] = maxOf(counts[node] ?: 0, count)
}
}
}
GCounter гарантує коректність злиття за рахунок того, що кожен вузол зберігає лише свій лічильник, а merge бере максимум по кожному вузлу. Це класичний приклад конфліктно-вільної структури, що використовується в децентралізованих системах.
Вибір стратегії залежить від характеру даних та сценаріїв використання. LWW оптимальний для додатків, де остання версія завжди має пріоритет — стрічка новин, сповіщення, статуси. Стратегія злиття підходить для структурованих документів, де кожне поле незалежне — профілі користувачів, форми, конфігурації. CRDT ідеальний для спільного редагування, списків та лічильників у розподілених системах.
При виборі стратегії оцінюються три фактори: узгодженість даних, продуктивність та складність реалізації. LWW дає максимальну продуктивність і мінімальну складність, але може втрачати дані. Merge забезпечує високу точність, але потребує механізму виявлення змін на рівні полів. CRDT гарантує математичну коректність, але накладає обмеження на типи даних та розмір метаданих.
| Стратегія | Втрата даних | Складність | Продуктивність | Приклад використання |
|---|---|---|---|---|
| LWW | Можлива | Низька | Висока | Стрічка новин, статуси |
| Merge | Мінімальна | Середня | Середня | Профілі, документи |
| CRDT | Немає | Висока | Середня-висока | Спільне редагування |
На практиці часто застосовується комбінований підхід: системи використовують LWW для метаданих, Merge для вмісту документів та CRDT для спискових структур. Firebase Firestore, наприклад, застосовує LWW для полів верхнього рівня та підтримує транзакції для атомарних оновлень. CouchDB використовує Merge зі зберіганням історії змін. Figma та Notion будують архітектуру на базі CRDT для багатокористувацького редагування в реальному часі.
Часті запитання
Вирішення конфліктів — це механізм, що визначає, яка версія даних вважається правильною при одночасних змінах одного об'єкта на різних пристроях. Система застосовує стратегію (LWW, Merge, CRDT) для вибору або об'єднання версій.
LWW вибирає одну повністю версію за часовою міткою, інша відкидається. Merge об'єднує зміни з обох версій на рівні окремих полів, що мінімізує втрату даних, але потребує складнішої реалізації та зберігання базової версії.
CRDT обирають для сценаріїв, де неприпустима втрата даних: спільне редагування, фінансові операції, списки завдань. LWW достатньо для некритичних даних — статуси, стрічка новин, кеші, де остання версія об'єктивно правильна.
Неправильне вирішення конфліктів викликає втрату користувацьких даних, що веде до негативних відгуків та відтоку. За даними дослідження University of Washington (2025), 67% користувачів припиняють використання додатку після двох випадків втрати введеної інформації через конфлікти синхронізації.
CouchDB та PouchDB мають вбудовану підтримку тристороннього злиття документів. Firebase Firestore підтримує транзакції для атомарних оновлень. RethinkDB та MongoDB потребують реалізації на рівні додатку через патерн «оптимістичне блокування» з версіонуванням.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.