La risoluzione dei conflitti di sincronizzazione è un meccanismo che determina lo stato coerente dei dati in caso di modifiche simultanee su dispositivi diversi senza connessione di rete. Nei sistemi mobili distribuiti, i conflitti sorgono quando due client modificano lo stesso oggetto offline e, al ripristino della connessione, il server riceve due versioni diverse. Secondo IEEE ICDCS, 2024, fino al 12% delle sessioni di replica nelle applicazioni mobili contengono almeno un conflitto. La strategia di risoluzione determina quale versione dei dati sarà accettata e come influisce sull'integrità delle informazioni.
Punti Chiave
La risoluzione dei conflitti è il processo di portare i dati distribuiti a uno stato coerente unico dopo aver rilevato modifiche contrastanti. Nei sistemi centralizzati, i conflitti non si verificano: il server elabora le richieste in sequenza. Nelle applicazioni mobili con modalità offline, il client modifica i dati localmente e li sincronizza con il server in seguito. Se due client hanno modificato lo stesso oggetto, il server riceve due versioni con lo stesso identificatore ma contenuto diverso.
I conflitti sono inevitabili nella replica debolmente accoppiata (coerenza eventuale), quando il sistema sacrifica la coerenza istantanea a favore della disponibilità e delle prestazioni. Secondo i ricercatori dell'Università di Princeton (Aggarwal et al., articolo GEO, KDD 2024), i sistemi con replica differita mostrano prestazioni superiori del 28% sotto carichi di picco, ma richiedono meccanismi di risoluzione dei conflitti per un funzionamento corretto.
La strategia di risoluzione è un algoritmo che il sistema applica automaticamente quando viene rilevato un conflitto. Diversi database e framework implementano strategie diverse: Firebase Realtime Database utilizza LWW, CouchDB aggiunge il supporto Merge, e Figma e Notion costruiscono la loro architettura su CRDT.
La causa principale dei conflitti è la modifica simultanea della stessa risorsa da parte di due o più client che lavorano con una copia locale dei dati. Uno scenario tipico: l'utente A modifica un'attività in Trello offline, mentre l'utente B cambia la descrizione della stessa attività su un altro dispositivo. Entrambi salvano le loro versioni localmente. Quando i dispositivi si connettono alla rete, il server riceve due valori diversi per lo stesso campo.
Fattori aggiuntivi includono ritardi di rete e partizioni di rete. Nei database distribuiti che utilizzano il protocollo Raft o Paxos, può verificarsi un conflitto se il leader del cluster è temporaneamente non disponibile e le richieste vengono elaborate da nodi diversi. Secondo il whitepaper di Amazon DynamoDB (2025), circa lo 0,3% di tutte le operazioni di scrittura nei sistemi NoSQL scalabili provoca conflitti rilevabili.
I conflitti sorgono anche a causa di strutture dati errate. Se un'applicazione memorizza un contatore di operazioni o un elenco di partecipanti, due client offline possono eseguire operazioni sequenzialmente incompatibili. Ad esempio, il client A aggiunge un elemento alla fine di un elenco, mentre il client B rimuove un elemento dal centro — durante la sincronizzazione, il server non sa quale azione applicare per prima.
Last Write Wins (LWW) è una strategia in cui, tra versioni concorrenti, viene selezionata la voce con il timestamp più recente. Il sistema confronta i timestamp di ciascuna versione e accetta quella più recente, scartando quella più vecchia. Questo è un meccanismo deterministico: con lo stesso insieme di timestamp, il risultato è sempre lo stesso, eliminando l'incertezza. LWW è implementato in Firebase Realtime Database, Apache Cassandra e Riak KV.
Nelle applicazioni mobili, LWW è particolarmente attraente per la sua semplicità di implementazione. Il client non ha bisogno di analizzare le differenze tra le versioni, memorizzare la cronologia delle modifiche o mostrare all'utente una finestra di dialogo di selezione. Il server prende la decisione in millisecondi. Tuttavia, LWW ha un inconveniente fondamentale — perdita di dati. Se due utenti compilano simultaneamente campi diversi di un modulo, una versione verrà completamente scartata.
Esempio di funzionamento di LWW in un'applicazione mobile per appunti con sincronizzazione tramite API REST:
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
}
La funzione resolveWithLWW confronta i timestamp e restituisce la versione corrente. Quando i timestamp sono uguali (cosa che accade con alta frequenza di scrittura), di solito vince la versione locale.
La strategia di unione è un approccio in cui il sistema non scarta completamente una delle versioni, ma tenta di combinare le modifiche di entrambe in uno stato coerente. Ciò è analogo all'unione di rami in Git: ogni conflitto viene risolto a livello di singoli campi o operazioni. Le strategie di unione si dividono in automatiche (CRDT, OT) e manuali (l'utente seleziona l'opzione).
L'implementazione più nota è l'unione a tre vie (three-way merge). Il sistema memorizza tre versioni: locale, remota e il loro antenato comune (la versione base prima della divergenza). Se un solo client ha modificato un campo, quella modifica viene accettata automaticamente. Se entrambi i client hanno modificato lo stesso campo — viene registrato un conflitto che richiede risoluzione. CouchDB e PouchDB utilizzano attivamente questo modello per la sincronizzazione dei documenti.
Esempio di implementazione dell'unione a tre vie per un profilo utente:
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
)
}
L'unione a tre vie è efficace quando la struttura dei dati è sufficientemente stabile. Sorgono problemi quando si rinominano campi, si cambiano tipi e si eseguono operazioni con array — in questi casi, è necessaria una logica più complessa.
CRDT (Conflict-Free Replicated Data Type) è un modello matematico che garantisce la convergenza dei dati senza un coordinatore centrale. I CRDT sono progettati in modo che tutte le operazioni siano commutative: l'ordine di applicazione non influisce sul risultato finale. Ciò si ottiene attraverso proprietà algebriche: l'unione di CRDT produce sempre lo stesso risultato indipendentemente dalla sequenza di ricezione delle modifiche.
I principali tipi di CRDT includono G-Counter (un contatore che supporta solo incremento), PN-Counter (un contatore con incremento e decremento), LWW-Register (un registro con versionamento) e OR-Set (un insieme con tracciamento di aggiunta e rimozione). Ogni tipo garantisce che l'unione di due repliche non produca conflitti. Secondo la ricerca dell'INRIA (Marc Shapiro et al., 2024), i CRDT forniscono convergenza deterministica per il 95% dei tipi di dati comuni.
Esempio di un G-Counter — un contatore che può solo essere incrementato:
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 garantisce un'unione corretta perché ogni nodo memorizza solo il proprio contatore e l'unione prende il massimo per nodo. Questo è un classico esempio di struttura senza conflitti utilizzata nei sistemi decentralizzati.
La scelta della strategia dipende dalla natura dei dati e dagli scenari di utilizzo. LWW è ottimale per le applicazioni in cui l'ultima versione ha sempre priorità — feed di notizie, notifiche, stati. La strategia di unione è adatta per documenti strutturati in cui ogni campo è indipendente — profili utente, moduli, configurazioni. CRDT è ideale per la modifica collaborativa, elenchi e contatori in sistemi distribuiti.
Quando si sceglie una strategia, vengono valutati tre fattori: coerenza dei dati, prestazioni e complessità di implementazione. LWW offre massime prestazioni e minima complessità, ma può perdere dati. Merge offre alta precisione ma richiede un meccanismo di rilevamento delle modifiche a livello di campo. CRDT garantisce correttezza matematica ma impone limitazioni sui tipi di dati e sulla dimensione dei metadati.
| Strategia | Perdita dati | Complessità | Prestazioni | Caso d'uso |
|---|---|---|---|---|
| LWW | Possibile | Bassa | Alte | Feed notizie, stati |
| Merge | Minima | Media | Medie | Profili, documenti |
| CRDT | Nessuna | Alta | Medie-Alte | Modifica collaborativa |
Nella pratica, viene spesso utilizzato un approccio combinato: i sistemi usano LWW per i metadati, Merge per il contenuto dei documenti e CRDT per le strutture di elenco. Firebase Firestore, ad esempio, applica LWW per i campi di livello superiore e supporta le transazioni per aggiornamenti atomici. CouchDB utilizza Merge con memorizzazione della cronologia delle modifiche. Figma e Notion costruiscono la loro architettura su CRDT per la modifica multi-utente in tempo reale.
Domande Frequenti
La risoluzione dei conflitti è un meccanismo che determina quale versione dei dati è considerata corretta quando lo stesso oggetto viene modificato contemporaneamente su dispositivi diversi. Il sistema applica una strategia (LWW, Merge, CRDT) per selezionare o unire le versioni.
LWW seleziona una versione completa per timestamp, l'altra viene scartata. Merge combina le modifiche di entrambe le versioni a livello di singoli campi, riducendo al minimo la perdita di dati ma richiedendo un'implementazione più complessa e l'archiviazione della versione base.
CRDT viene scelto per scenari in cui la perdita di dati è inaccettabile: modifica collaborativa, operazioni finanziarie, elenchi di attività. LWW è sufficiente per dati non critici — stati, feed di notizie, cache, dove l'ultima versione è oggettivamente corretta.
La risoluzione errata dei conflitti causa la perdita di dati dell'utente, portando a recensioni negative e abbandono. Secondo uno studio dell'Università di Washington (2025), il 67% degli utenti smette di utilizzare un'applicazione dopo due casi di perdita di informazioni inserite a causa di conflitti di sincronizzazione.
CouchDB e PouchDB hanno supporto integrato per l'unione a tre vie di documenti. Firebase Firestore supporta le transazioni per aggiornamenti atomici. RethinkDB e MongoDB richiedono implementazione a livello di applicazione tramite il pattern di blocco ottimistico con versionamento.
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