Merge Strategy è una strategia di fusione dei dati in cui le modifiche in conflitto da diverse versioni vengono combinate in un unico stato coerente invece di sostituire una versione con un'altra. A differenza di Last Write Wins, la fusione tenta di preservare le modifiche da tutti i rami, minimizzando la perdita di dati. Secondo la documentazione di Apache CouchDB, 2025, la fusione a tre vie (three-way merge) è il meccanismo standard di risoluzione dei conflitti nei database orientati ai documenti. La fusione a tre vie utilizza una versione di base comune per determinare quali campi sono stati modificati da ciascun client.
Punti chiave
Merge Strategy è un insieme di algoritmi che combinano versioni di dati in conflitto invece di sceglierne una. Nelle applicazioni mobili, la fusione viene utilizzata quando due client modificano indipendentemente diversi campi o proprietà dello stesso oggetto. Invece di scartare completamente la versione più vecchia (come in LWW), il sistema analizza le differenze a livello di singoli campi e produce un oggetto risultante contenente le modifiche di entrambe le versioni.
Differenza chiave tra fusione e LWW è la preservazione delle modifiche di ciascun utente a condizione che non si contraddicano a vicenda. Se l'utente A ha modificato il nome dell'attività e l'utente B ha modificato la descrizione, la fusione preserva entrambe le modifiche. Se entrambi hanno modificato lo stesso campo — viene registrato un conflitto che richiede risoluzione. Questo rende la fusione preferibile per le applicazioni in cui gli utenti lavorano in modo collaborativo sugli stessi dati.
Secondo un rapporto del Stripe Engineering Blog (2025), l'implementazione di Merge Strategy invece di LWW ha ridotto il numero di reclami degli utenti sulla perdita di dati del 76% nella loro applicazione mobile di gestione progetti. Tuttavia, il tempo di elaborazione dei conflitti è aumentato di 15–30 ms, considerato un prezzo accettabile per l'integrità dei dati.
La fusione a tre vie (three-way merge) è l'implementazione più comune di Merge Strategy. Il meccanismo opera con tre versioni di dati: base (stato prima della divergenza), locale (versione del client corrente) e remota (versione del server). Il sistema confronta ogni campo delle versioni locale e remota con la base per determinare quale lato ha modificato quali campi.
La logica decisionale è semplice: se solo un client ha modificato un campo (rispetto alla base), la sua modifica viene accettata automaticamente. Se entrambi i client hanno modificato lo stesso campo — viene registrato un conflitto che può essere risolto automaticamente (per priorità) o delegato all'utente. Se nessun client ha modificato il campo — rimane il valore base. Questo approccio garantisce che le modifiche indipendenti non vengano né perse né entrino in conflitto.
L'algoritmo di fusione a tre vie a livello di dizionario di campi:
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
}
La funzione threeWayMerge elabora sequenzialmente tutte le chiavi delle tre versioni. Se il valore locale corrisponde alla base — viene accettata la modifica remota. Se il valore remoto corrisponde alla base — viene accettata la modifica locale. Se entrambi differiscono dalla base ma sono uguali tra loro — viene accettato uno qualsiasi. Un conflitto reale viene registrato solo quando entrambi i lati hanno modifiche diverse.
La risoluzione automatica viene applicata quando le modifiche non si sovrappongono o quando il sistema può determinare il valore corretto in base a regole. Ad esempio, per i campi numerici è possibile selezionare il valore massimo, per i campi di testo — concatenazione o la versione più recente. CouchDB utilizza la fusione automatica per i campi dei documenti JSON e per gli array — concatenazione con rimozione dei duplicati.
La risoluzione manuale è necessaria quando due utenti hanno modificato lo stesso campo in modo diverso. In questo caso, l'applicazione mostra un dialogo con tre opzioni: \u201caccetta versione locale\u201d, \u201caccetta versione remota\u201d o \u201cfondi manualmente\u201d. Secondo una ricerca della CMU (Carnegie Mellon University, 2024), la risoluzione manuale riduce la soddisfazione dell'utente del 40%, quindi la fusione automatica dovrebbe essere massimizzata.
Strategie di risoluzione per diversi tipi di campi:
| Tipo di campo | Strategia automatica | Alternativa manuale |
|---|---|---|
| Numero (contatore) | Prendere il massimo | Mostra entrambi i valori |
| Testo (stringa) | Seleziona per ora | Editor evidenziato |
| Booleano | Priorità per ruoli | Tre opzioni di selezione |
| Array (lista) | Unione con deduplicazione | Selezione elemento per elemento |
| Oggetto nidificato | Fusione ricorsiva | Mostra diff |
Consideriamo l'implementazione di Merge Strategy per un profilo utente in un'applicazione mobile con sincronizzazione tramite REST API. Il profilo contiene nome, email, avatar e impostazioni di notifica. Ogni campo può essere modificato indipendentemente su diversi dispositivi dell'utente.
Classe dati del profilo con versionamento a livello di campi:
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
)
}
La funzione mergeProfiles elabora indipendentemente ogni campo del profilo, selezionando la versione che differisce dalla base. In caso di conflitto (entrambi differiscono dalla base), la priorità è determinata dalle regole dell'applicazione. Nell'esempio, per avatarUrl viene data priorità alla versione remota, per i campi rimanenti — a quella locale.
CouchDB e PouchDB sono i database più noti con supporto integrato di Merge Strategy. Durante la replica dei documenti, CouchDB utilizza la replica multi-thread con rilevamento dei conflitti a livello di documento. La versione di base è memorizzata nella cronologia delle revisioni e, in caso di conflitto, il sistema preserva tutti i rami in conflitto e fornisce all'applicazione un'API per risolverli tramite il meccanismo di fusione.
In Firebase Firestore, la fusione è implementata tramite transazioni con blocco ottimistico. Lo sviluppatore può specificare che determinati campi devono essere aggiornati atomicamente utilizzando FieldValue.serverTimestamp() e FieldValue.arrayUnion(). Tuttavia, Firestore non supporta la fusione a tre vie completa — in caso di conflitto, la transazione viene riprovata con nuovi dati, che equivale a un nuovo tentativo piuttosto che a una vera fusione.
Per le applicazioni mobili su Kotlin Multiplatform e React Native, Merge Strategy è implementata lato client. Il database locale (SQLite, Realm) memorizza la versione di ciascun documento e, durante la sincronizzazione, il client carica la versione del server ed esegue la fusione localmente prima di inviare il risultato. Questo approccio garantisce l'integrità dei dati anche durante il funzionamento offline prolungato quando si accumulano più conflitti.
Domande frequenti
Merge Strategy è un approccio alla risoluzione dei conflitti in cui le modifiche da diverse versioni vengono combinate in un unico stato. A differenza di LWW, la fusione preserva le modifiche da entrambi i rami se non si contraddicono a livello di campi.
La fusione a tre vie utilizza una versione di base (stato prima della divergenza) per determinare quali campi ciascun client ha modificato. La fusione a due vie confronta solo due versioni senza conoscere lo stato originale, portando più spesso a falsi conflitti.
CouchDB e PouchDB hanno supporto integrato per la fusione a tre vie. Firebase Firestore richiede implementazione a livello di transazioni. MongoDB e Realm offrono meccanismi di blocco ottimistico, ma non la fusione automatica completa.
La fusione non è adatta per dati dove la velocità di elaborazione è critica (oltre 1000 conflitti al secondo), per dati in streaming (log, eventi) e per casi in cui le modifiche sono fondamentalmente incompatibili (diverse versioni dello schema). In questi casi, LWW o CRDT saranno più efficienti.
L'implementazione include tre passaggi: memorizzare la versione di base durante il caricamento dei dati dal server, rilevare le modifiche a livello di campi durante il salvataggio e chiamare l'algoritmo di fusione durante la sincronizzazione. Per semplificare, utilizzare le librerie JSON Patch o CRDT.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche