Merge Strategy — strategia de îmbinare a datelor, prin care modificările conflictuale din diferite versiuni sunt combinate într-o stare coerentă unică, în loc să înlocuiască o versiune cu alta. Spre deosebire de Last Write Wins, îmbinarea încearcă să păstreze modificările din toate ramurile, minimizând pierderea de date. Potrivit Apache CouchDB documentation, 2025, îmbinarea pe trei căi (three-way merge) este mecanismul standard de rezolvare a conflictelor în bazele de date orientate pe documente. Îmbinarea pe trei căi utilizează o versiune de bază comună pentru a determina ce câmpuri au fost modificate de fiecare client.
Principalele
Merge Strategy — ansamblu de algoritmi care combină versiunile conflictuale de date în loc să selecteze una dintre ele. În aplicațiile mobile, Merge este utilizat când doi clienți editează independent diferite câmpuri sau proprietăți ale aceluiași obiect. În loc să renunțe complet la versiunea mai veche (ca în LWW), sistemul analizează diferențele la nivel de câmpuri individuale și formează un obiect rezultat care conține modificări din ambele versiuni.
Diferența cheie dintre Merge și LWW — păstrarea modificărilor fiecărui utilizator cu condiția ca acestea să nu se contrazică reciproc. Dacă utilizatorul A a modificat denumirea sarcinii, iar utilizatorul B — descrierea, Merge va păstra ambele modificări. Dacă ambii au modificat același câmp — se înregistrează un conflict care necesită rezolvare. Acest lucru face Merge preferabil pentru aplicațiile în care utilizatorii lucrează împreună cu aceleași date.
Potrivit raportului Stripe Engineering Blog (2025), implementarea Merge Strategy în loc de LWW a redus numărul plângerilor utilizatorilor privind pierderea datelor cu 76% în aplicația lor mobilă de gestionare a proiectelor. Cu toate acestea, timpul de procesare a conflictelor a crescut cu 15–30 ms, ceea ce este considerat un preț acceptabil pentru păstrarea informațiilor.
Îmbinarea pe trei căi (three-way merge) — cea mai răspândită implementare a Merge Strategy. Mecanismul operează cu trei versiuni de date: de bază (base — starea înainte de divergență), locală (local — versiunea clientului curent) și de la distanță (remote — versiunea de pe server). Sistemul compară fiecare câmp al versiunilor locală și de la distanță cu cea de bază pentru a determina ce parte a modificat ce câmpuri.
Logica decizională este simplă: dacă un câmp a fost modificat doar de un client (față de bază), modificarea sa este acceptată automat. Dacă ambii clienți au modificat același câmp — se înregistrează un conflict care poate fi rezolvat automat (după prioritate) sau transmis utilizatorului. Dacă niciun client nu a modificat câmpul — rămâne valoarea de bază. Această abordare garantează că modificările independente nu se pierd și nu intră în conflict.
Algoritmul îmbinării pe trei căi la nivel de dicționar de câmpuri:
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 -> // conflict real
resolveConflict(key, localVal, remoteVal)
}
}
return result
}
Funcția threeWayMerge procesează secvențial toate cheile din cele trei versiuni. Dacă valoarea locală coincide cu cea de bază — se acceptă modificarea de la distanță. Dacă valoarea de la distanță coincide cu cea de bază — se acceptă modificarea locală. Dacă ambele diferă de bază, dar sunt egale între ele — oricare. Un conflict real se înregistrează doar la modificări diferite din ambele părți.
Rezolvarea automată se aplică atunci când modificările nu se intersectează sau când sistemul poate determina valoarea corectă pe baza regulilor. De exemplu, pentru câmpurile numerice se poate selecta valoarea maximă, pentru cele text — concatenarea sau versiunea mai nouă. CouchDB utilizează îmbinarea automată pentru câmpurile documentelor JSON, iar pentru array-uri — concatenarea cu eliminarea duplicatelor.
Rezolvarea manuală este necesară atunci când doi utilizatori au modificat același câmp în mod diferit. În acest caz, aplicația afișează un dialog cu trei opțiuni: „acceptă versiunea locală”, „acceptă versiunea de la distanță” sau „îmbină manual”. Autorii studiului CMU (Carnegie Mellon University, 2024) menționează că rezolvarea manuală reduce satisfacția utilizatorului cu 40%, prin urmare îmbinarea automată trebuie maximizată.
Strategii de rezolvare pentru diferite tipuri de câmpuri:
| Tip câmp | Strategie automată | Alternativă manuală |
|---|---|---|
| Număr (contor) | Ia valoarea maximă | Afișează ambele valori |
| Text (șir) | Alege după timp | Editor cu evidențiere |
| Valoare logică | Prioritate pe roluri | Trei opțiuni de alegere |
| Array (listă) | Unire cu deduplicare | Selectare element cu element |
| Obiect imbricat | Îmbinare recursivă | Afișează diff |
Să examinăm implementarea Merge Strategy pentru profilul unui utilizator într-o aplicație mobilă cu sincronizare prin REST API. Profilul conține nume, email, avatar și setări de notificare. Fiecare câmp poate fi modificat independent pe diferite dispozitive ale utilizatorului.
Clasa de date a profilului cu versionare la nivel de câmpuri:
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
)
}
Funcția mergeProfiles procesează independent fiecare câmp al profilului, selectând acea versiune care diferă de cea de bază. La conflict (ambele diferă de bază), prioritatea este determinată de regulile aplicației. În exemplu, pentru avatarUrl prioritatea se acordă versiunii de la distanță, pentru restul câmpurilor — celei locale.
CouchDB și PouchDB — cele mai cunoscute baze de date cu suport încorporat pentru Merge Strategy. La replicarea documentelor, CouchDB utilizează replicarea multi-thread cu detectarea conflictelor la nivel de document. Versiunea de bază este stocată în istoricul reviziilor, iar la conflict, sistemul păstrează toate ramurile conflictuale și pune la dispoziția aplicației o API pentru rezolvarea lor prin mecanismul de îmbinare.
În Firebase Firestore, Merge este implementat prin tranzacții cu blocare optimistă. Dezvoltatorul poate specifica că anumite câmpuri trebuie actualizate atomic, utilizând FieldValue.serverTimestamp() și FieldValue.arrayUnion(). Totuși, Firestore nu suportă îmbinarea completă pe trei căi — la conflict, tranzacția se repetă cu date noi, ceea ce este echivalent cu o reîncercare, nu cu o îmbinare reală.
Pentru aplicațiile mobile pe Kotlin Multiplatform și React Native, Merge Strategy este implementată pe partea clientului. Baza locală (SQLite, Realm) stochează versiunea fiecărui document, iar la sincronizare, clientul încarcă versiunea de pe server și execută îmbinarea local înainte de trimiterea rezultatului. Această abordare asigură păstrarea datelor chiar și în timpul lucrului offline prelungit, când se acumulează mai multe conflicte.
Întrebări frecvente
Merge Strategy — abordare de rezolvare a conflictelor prin care modificările din diferite versiuni sunt combinate într-o stare unică. Spre deosebire de LWW, Merge păstrează modificările din ambele ramuri dacă acestea nu se contrazic la nivel de câmpuri.
Îmbinarea pe trei căi utilizează versiunea de bază (starea înainte de divergență) pentru a determina ce câmpuri a modificat fiecare client. Îmbinarea pe două căi compară doar două versiuni, necunoscând starea inițială, ceea ce duce mai des la conflicte false.
CouchDB și PouchDB au suport încorporat pentru îmbinarea pe trei căi. Firebase Firestore necesită implementare la nivel de tranzacții. MongoDB și Realm oferă mecanisme de blocare optimistă, dar nu îmbinare automată completă.
Merge nu se potrivește pentru datele unde contează viteza de procesare (peste 1000 de conflicte pe secundă), pentru datele în flux (loguri, evenimente) și pentru cazurile când modificările sunt fundamental incompatibile (diferite versiuni de schemă de date). În aceste cazuri, LWW sau CRDT vor fi mai eficiente.
Implementarea include trei pași: stocarea versiunii de bază la încărcarea datelor de pe server, detectarea modificărilor la nivel de câmpuri la salvare și apelarea algoritmului de îmbinare la sincronizare. Pentru simplificare, utilizați bibliotecile JSON Patch sau CRDT.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și