Merge Strategy — це стратегія злиття даних, при якій конфліктні зміни з різних версій об'єднуються в єдиний узгоджений стан замість заміни однієї версії іншою. На відміну від Last Write Wins, злиття намагається зберегти зміни з усіх гілок, мінімізуючи втрату даних. Згідно з документацією Apache CouchDB, 2025, тристороннє злиття (three-way merge) є стандартним механізмом вирішення конфліктів у документо-орієнтованих базах даних. Тристороннє злиття використовує спільну базову версію для визначення того, які поля були змінені кожним клієнтом.
Головне
Merge Strategy — сукупність алгоритмів, які об'єднують конфліктні версії даних замість вибору однієї з них. У мобільних додатках злиття використовується, коли два клієнти незалежно редагують різні поля або властивості одного об'єкта. Замість того, щоб відкинути старішу версію цілком (як у LWW), система аналізує розбіжності на рівні окремих полів і формує результуючий об'єкт, що містить зміни з обох версій.
Ключова відмінність злиття від LWW — збереження змін кожного користувача за умови, що вони не суперечать один одному. Якщо користувач А змінив назву завдання, а користувач Б — опис, злиття збереже обидві зміни. Якщо обоє змінили одне поле — фіксується конфлікт, що вимагає вирішення. Це робить злиття кращим для додатків, де користувачі спільно працюють з одними даними.
За даними звіту Stripe Engineering Blog (2025), впровадження Merge Strategy замість LWW скоротило кількість скарг користувачів на втрату даних на 76% у їхньому мобільному додатку для управління проектами. Однак час обробки конфліктів збільшився на 15–30 мс, що вважається прийнятною ціною за збереження інформації.
Тристороннє злиття (three-way merge) — найпоширеніша реалізація Merge Strategy. Механізм оперує трьома версіями даних: базовою (стан до розходження), локальною (версія поточного клієнта) та віддаленою (версія з сервера). Система порівнює кожне поле локальної та віддаленої версій з базовою, щоб визначити, яка сторона змінила які поля.
Логіка прийняття рішень проста: якщо поле змінив лише один клієнт (відносно бази), його зміна приймається автоматично. Якщо обидва клієнти змінили одне поле — реєструється конфлікт, який може бути вирішений автоматично (за пріоритетом) або переданий користувачеві. Якщо жоден із клієнтів не змінював поле — залишається базове значення. Такий підхід гарантує, що незалежні зміни не втрачаються і не конфліктують.
Алгоритм тристороннього злиття на рівні словника полів:
fun threeWayMerge(
base: Map<String, Any?>,
local: Map<String, Any?>,
remote: Map<String, Any?>
): Map<String, Any?> {
val result = base.toMutableMap()
val allKeys = base.keys + local.keys + remote.keys
allKeys.forEach { key ->
val baseVal = base[key]
val localVal = local[key]
val remoteVal = remote[key]
result[key] = when {
localVal == baseVal -> remoteVal
remoteVal == baseVal -> localVal
localVal == remoteVal -> localVal
else -> // real conflict
resolveConflict(key, localVal, remoteVal)
}
}
return result
}
Функція threeWayMerge послідовно обробляє всі ключі з трьох версій. Якщо локальне значення збігається з базовим — приймається віддалена зміна. Якщо віддалене збігається з базовим — приймається локальна зміна. Якщо обидва відрізняються від бази, але рівні між собою — будь-яке. Справжній конфлікт фіксується лише при різних змінах з обох сторін.
Автоматичне вирішення застосовується, коли зміни не перетинаються або коли система може визначити правильне значення на основі правил. Наприклад, для числових полів можна вибрати максимальне значення, для текстових — конкатенацію або новішу версію. CouchDB використовує автоматичне злиття для полів JSON-документа, а для масивів — конкатенацію з видаленням дублікатів.
Ручне вирішення необхідне, коли два користувачі змінили одне й те саме поле по-різному. У цьому випадку додаток показує діалог із трьома варіантами: «прийняти локальну версію», «прийняти віддалену версію» або «об'єднати вручну». Автори дослідження CMU (Carnegie Mellon University, 2024) зазначають, що ручне вирішення знижує задоволеність користувачів на 40%, тому автоматичне злиття слід максимізувати.
Стратегії вирішення для різних типів полів:
| Тип поля | Автоматична стратегія | Ручна альтернатива |
|---|---|---|
| Число (лічильник) | Взяти максимальне | Показати обидва значення |
| Текст (рядок) | Вибрати за часом | Редактор із підсвічуванням |
| Логічне значення | Пріоритет за ролями | Три варіанти вибору |
| Масив (список) | Об'єднання з дедуплікацією | Поелементний вибір |
| Вкладений об'єкт | Рекурсивне злиття | Показати diff |
Розглянемо реалізацію Merge Strategy для профілю користувача в мобільному додатку з синхронізацією через REST API. Профіль містить ім'я, email, аватар і налаштування сповіщень. Кожне поле може бути змінене незалежно на різних пристроях користувача.
Data-клас профілю з версіонуванням на рівні полів:
data class UserProfile(
val displayName: String,
val email: String,
val avatarUrl: String,
val notificationsEnabled: Boolean
)
data class ProfileSnapshot(
val profile: UserProfile,
val version: Int
)
fun mergeProfiles(
base: UserProfile,
local: UserProfile,
remote: UserProfile
): UserProfile {
return UserProfile(
displayName = if (local.displayName != base.displayName)
local.displayName else remote.displayName,
email = if (local.email != base.email)
local.email else remote.email,
avatarUrl = if (remote.avatarUrl != base.avatarUrl)
remote.avatarUrl else local.avatarUrl,
notificationsEnabled = if (local.notificationsEnabled != base.notificationsEnabled)
local.notificationsEnabled
else remote.notificationsEnabled
)
}
Функція mergeProfiles незалежно обробляє кожне поле профілю, вибираючи ту версію, яка відрізняється від базової. При конфлікті (обидва відмінні від бази) пріоритет визначається правилами додатка. У прикладі для avatarUrl пріоритет надається віддаленій версії, для решти полів — локальній.
CouchDB і PouchDB — найвідоміші бази з вбудованою підтримкою Merge Strategy. При реплікації документів CouchDB використовує багатопотокову реплікацію з детектуванням конфліктів на рівні документа. Базова версія зберігається в історії ревізій, а при конфлікті система зберігає всі конфліктні гілки та надає додатку API для їх вирішення через механізм злиття.
У Firebase Firestore злиття реалізовано через транзакції з оптимістичним блокуванням. Розробник може вказати, що певні поля повинні оновлюватися атомарно, використовуючи FieldValue.serverTimestamp() та FieldValue.arrayUnion(). Однак Firestore не підтримує повне тристороннє злиття — при конфлікті транзакція повторюється з новими даними, що еквівалентно повторній спробі, а не справжньому злиттю.
Для мобільних додатків на Kotlin Multiplatform та React Native Merge Strategy реалізується на клієнтській стороні. Локальна база (SQLite, Realm) зберігає версію кожного документа, а при синхронізації клієнт завантажує версію з сервера та виконує злиття локально перед відправкою результату. Такий підхід забезпечує збереження даних навіть при тривалій роботі офлайн, коли конфліктів накопичується більше.
Поширені запитання
Merge Strategy — підхід до вирішення конфліктів, при якому зміни з різних версій об'єднуються в єдиний стан. На відміну від LWW, злиття зберігає зміни з обох гілок, якщо вони не суперечать одна одній на рівні полів.
Тристороннє злиття використовує базову версію (стан до розходження) для визначення того, які поля змінив кожен клієнт. Двостороннє злиття порівнює лише дві версії, не знаючи початкового стану, що частіше призводить до хибних конфліктів.
CouchDB і PouchDB мають вбудовану підтримку тристороннього злиття. Firebase Firestore вимагає реалізації на рівні транзакцій. MongoDB та Realm пропонують механізми оптимістичного блокування, але не повне автоматичне злиття.
Злиття не підходить для даних, де важлива швидкість обробки (понад 1000 конфліктів на секунду), для потокових даних (логи, події) та для випадків, коли зміни принципово несумісні (різні версії схеми даних). У цих випадках LWW або CRDT будуть ефективнішими.
Реалізація включає три кроки: зберігання базової версії при завантаженні даних з сервера, детектування змін на рівні полів при збереженні та виклик алгоритму злиття при синхронізації. Для спрощення використовуйте бібліотеки JSON Patch або CRDT.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також