Conflict Resolution in Multi-Leader Replication
Detecting and resolving write conflicts in multi-leader replication, including last-write-wins and merge strategies.
Conflicts in Multi-Leader Replication
Two leaders accept different writes to the same data at the same time, before they have had a chance to replicate to each other.
User opens app on Phone → writes to Leader A → name = "John" User opens app on Laptop → writes to Leader B → name = "Jane" Both happen at t=10ms before A and B have synced. Now A thinks name = "John" Now B thinks name = "Jane" Who is correct? → Nobody knows. This is a conflict.
Conflicts Detection
B already has: name = "Jane" written at t=10ms by Leader B A is sending: name = "John" written at t=10ms by Leader A Same row. Same column. Different value. Different origin. → CONFLICT DETECTED
5 Conflict Resolution Strategies
LWW - Last Write Wins
Attach a timestamp to every write. The write with the latest timestamp wins. Older one is silently discarded.
Leader A write: name = "John" timestamp = 10:00:00.100 Leader B write: name = "Jane" timestamp = 10:00:00.105 Jane wins because 105 > 100. John is silently dropped.
FWW - Las Write Wins
Opposite of LWW. The earliest timestamp wins. Any later write to same data is rejected.
Leader A write: name = "John" timestamp = 100 ← wins, came first Leader B write: name = "Jane" timestamp = 105 ← rejected
- simple - used everywhere - in cassandra
- clock skew problem - [time clock chapter]
Merge / Union the Values
Instead of picking one winner, keep both values and merge them together.
Leader A: shopping_cart = { milk, bread } Leader B: shopping_cart = { milk, eggs } Conflict detected on shopping_cart. Merge result: shopping_cart = { milk, bread, eggs } → Union of both sets. No data lost.
- only works for set-like data
Leader A: account_balance = 500 Leader B: account_balance = 300 Merge = ??? 500 + 300 = 800? Wrong. 500 ∪ 300 = {500, 300}? Meaningless.
- No Data Loss - collaborative apps - e commerce cart
Custom Logic - Application will Decide
The database does not resolve the conflict. It stores both conflicting values and hands them to the application layer to decide.
Database stores: name = { "John" [from A], "Jane" [from B] } ← both kept, flagged as conflict Next time application reads name: App receives both values and the conflict flag App shows user: "Conflict detected. Which name is correct?" User picks "Jane" App writes final resolved value back
- Custom Logic should be correct - user has to resolve for every conflict