Conflict Resolution Strategies
Write Conflicts in Distributed Systems
Read aloud is unavailable in this browser.
The Inevitable Reality of Distributed Writes
In a perfect world, every write to a database would happen in perfect isolation—one at a time, in a well-defined order, with instant global visibility. Reality, however, is far more complex. The moment you distribute data across multiple nodes, replicas, or data centers, you enter a world where write conflicts are not just possible—they are inevitable.
Understanding write conflicts is fundamental to designing robust distributed systems. These conflicts arise not from bugs or poor design, but from the fundamental physics of distributed computing: information takes time to travel, nodes can fail independently, and the speed of light is finite. Every distributed database, every multi-master replication setup, every collaborative editing system must confront and resolve these conflicts.
By the end of this page, you will understand the fundamental nature of write conflicts, why they occur in distributed systems, how to identify and classify them, and the profound impact they have on system design. This foundation is essential before we explore specific resolution strategies in subsequent pages.
The Anatomy of a Write Conflict
A write conflict occurs when two or more concurrent write operations target the same piece of data without coordination, resulting in a state where the system cannot deterministically decide which value should prevail. Let's dissect the precise conditions that create conflicts.
The Three Prerequisites for Conflict:
For a write conflict to occur, three conditions must be present simultaneously:
-
Concurrency — Two or more write operations happen 'at the same time' in the distributed sense (neither is aware of the other when it begins)
-
Same Target — Both writes affect the same logical data item (the same key, the same row, the same document field)
-
No Coordination — The operations proceed without a synchronization mechanism that would serialize them (no locks, no leader election, no consensus)
Why 'Concurrent' Is Complex:
In distributed systems, 'concurrent' doesn't mean 'at the exact same nanosecond.' It means causally independent—neither operation could have influenced the other. Consider:
- Client A writes to Replica 1 in New York at 10:00:00.000 UTC
- Client B writes to Replica 2 in London at 10:00:00.001 UTC
Are these concurrent? The answer depends on perspective:
- Physical time: They're 1 millisecond apart
- Logical time: If neither replica knew about the other's write when it happened, they're concurrent in the distributed systems sense
This distinction is crucial. Physical clocks can drift (sometimes by seconds), network latency varies, and there's no global time authority in a distributed system. Two operations that appear sequential in wall-clock time may be logically concurrent if the causal relationship cannot be established.
Leslie Lamport's seminal 1978 paper 'Time, Clocks, and the Ordering of Events in a Distributed System' formalized this: event A 'happens before' event B (written A → B) if A could have causally influenced B. Two events are concurrent if neither happened before the other (A || B). This is the theoretical foundation for understanding conflicts.
Sources of Write Conflicts
Write conflicts emerge from specific architectural patterns in distributed systems. Understanding these sources helps you anticipate where conflicts will occur and design appropriate resolution strategies.
- •Multi-Master Replication — When multiple nodes can accept writes for the same data, a user in Europe writing to a European replica while a user in Asia writes to an Asian replica creates immediate conflict potential. Popular in: CouchDB, Cassandra, DynamoDB (with certain configurations).
- •Leaderless Replication — Systems like Amazon's Dynamo-style databases (Riak, Cassandra) allow any node to accept writes. Without a leader to serialize writes, concurrent updates to the same key naturally conflict.
- •Offline-First Applications — Mobile apps, collaborative tools, and edge computing systems that work offline accumulate changes locally, then sync when connectivity returns. Conflicts arise when multiple users modified the same data while disconnected.
- •Partition Tolerance (Network Splits) — During a network partition, different sides of the split may continue accepting writes. When the partition heals, the system discovers conflicting writes that occurred in isolation.
- •Async Replication Lag — Even in leader-follower systems, if failover occurs before all writes replicate, the new leader may have a stale state. Writes to the new leader conflict with (lost) writes to the old leader.
| Architecture | Conflict Risk | Conflict Frequency | Typical Resolution |
|---|---|---|---|
| Single Leader (sync replication) | Very Low | Rare (only during failover) | Rollback or manual |
| Single Leader (async replication) | Low | Low (lag-induced) | Last-write-wins or rollback |
| Multi-Leader | High | Frequent | LWW, vector clocks, or CRDTs |
| Leaderless (quorum) | Medium-High | Moderate | Read repair, anti-entropy |
| Offline-First | Very High | Very Frequent | Operational transforms, CRDTs |
The Trade-off Spectrum:
Every architectural decision that increases availability or reduces latency also increases conflict potential:
- Strong consistency: Zero conflicts, but higher latency and lower availability
- Eventual consistency: Lower latency, higher availability, but conflicts are guaranteed
This isn't a flaw—it's a fundamental trade-off governed by the CAP theorem. The key is choosing the right point on this spectrum for your use case and having a robust strategy for the conflicts you'll encounter.
Classification of Conflict Types
Not all conflicts are created equal. Understanding the different types of conflicts helps you choose appropriate resolution strategies and design data models that minimize harmful conflicts.
Write-Write Conflicts (also called update conflicts) occur when two operations concurrently modify the same data item.
Example Scenario:
- User A sets
balance = balance + 100(deposit $100) - User B sets
balance = balance - 50(withdraw $50) - Both read
balance = 500before their operation - Both write: A writes
600, B writes450
The Problem: If we take either value, we lose the other operation. The correct final balance should be 550, but neither 600 nor 450 is correct.
Characteristics:
- Most common type of conflict
- Often requires semantic understanding to resolve correctly
- May result in lost updates if naively resolved
- Particularly dangerous for counter/accumulator data
1234567891011121314151617181920
// Initial state: { balance: 500 } // Client A's operation (runs on Replica 1)function deposit(amount) { const current = read("balance"); // reads 500 write("balance", current + amount); // writes 600}deposit(100); // Client B's operation (runs on Replica 2, concurrently)function withdraw(amount) { const current = read("balance"); // reads 500 (hasn't seen A's write) write("balance", current - amount); // writes 450}withdraw(50); // After replication sync:// Replica 1 has: balance = 600// Replica 2 has: balance = 450// CONFLICT: Which is correct? Neither! Should be 550.Detecting Conflicts
Before you can resolve a conflict, you must detect it. Distributed systems employ various mechanisms to identify when concurrent writes have created divergent states.
- •Version Vectors — Each replica maintains a vector of version counters. When receiving a write, compare vectors to detect concurrent modifications.
- •Timestamps Comparison — Compare physical or logical timestamps. Non-equal timestamps for same data version indicate potential conflict.
- •Hash Comparison — Compute hashes of data states. Differing hashes for same logical key indicate divergence.
- •Merkle Trees — Hierarchical hash structures allow efficient detection of divergent subtrees across large datasets.
- •Read Repair — During reads, compare responses from multiple replicas. Divergent values reveal conflicts that are then resolved.
- •Quorum Reads — Reading from multiple nodes exposes version inconsistencies when values don't match.
- •Anti-Entropy Processes — Background processes periodically compare replica states and identify divergences.
- •Client-Side Detection — Smart clients compare cached versions with server responses to detect conflicts.
Version Vectors in Depth:
Version vectors are the most rigorous conflict detection mechanism. Each replica maintains a vector: {Replica1: 5, Replica2: 3, Replica3: 7}. When data is modified:
- The modifying replica increments its own counter
- The vector is attached to the data
- On sync, vectors are compared:
- If Vector A dominates Vector B (every element ≥), A supersedes B (no conflict)
- If neither dominates, the writes are concurrent (CONFLICT)
Example:
- Vector A:
{R1: 5, R2: 3, R3: 7} - Vector B:
{R1: 4, R2: 4, R3: 7}
A has R1:5 > R1:4, but B has R2:4 > R2:3. Neither dominates → Concurrent writes detected!
1234567891011121314151617181920212223242526272829303132333435363738
interface VersionVector { [replicaId: string]: number;} type ComparisonResult = 'DOMINATES' | 'DOMINATED_BY' | 'CONCURRENT' | 'EQUAL'; function compareVersionVectors( vectorA: VersionVector, vectorB: VersionVector): ComparisonResult { const allKeys = new Set([ ...Object.keys(vectorA), ...Object.keys(vectorB) ]); let aDominates = true; let bDominates = true; for (const key of allKeys) { const a = vectorA[key] ?? 0; const b = vectorB[key] ?? 0; if (a < b) aDominates = false; if (b < a) bDominates = false; } if (aDominates && bDominates) return 'EQUAL'; if (aDominates) return 'DOMINATES'; if (bDominates) return 'DOMINATED_BY'; return 'CONCURRENT'; // Neither dominates = CONFLICT} // Usage example:const vectorA: VersionVector = { replica1: 5, replica2: 3, replica3: 7 };const vectorB: VersionVector = { replica1: 4, replica2: 4, replica3: 7 }; const result = compareVersionVectors(vectorA, vectorB);console.log(result); // Output: 'CONCURRENT' - Conflict detected!The Impact of Unresolved Conflicts
When conflicts go unresolved or are resolved incorrectly, the consequences cascade through your system. Understanding these impacts underscores why robust conflict resolution is not optional.
- •Data Loss — The most direct impact. When conflicting writes are resolved by 'picking one,' the other write is lost. For banking transactions, this could mean real money disappearing. For medical records, it could mean lost patient information.
- •Data Corruption — Naive merge strategies can create data that neither original write intended. Merging
{balance: 600}and{balance: 450}might produce{balance: 525}(average), which is still wrong. - •Inconsistent Views — Different users/services see different 'truth.' User A sees their edit succeeded; User B sees something different. This erodes trust and causes confusion.
- •Audit Trail Gaps — For compliance-heavy systems (finance, healthcare), losing the history of how data changed is not just inconvenient—it may be legally problematic.
- •Cascading Failures — Inconsistent data can trigger downstream errors. An inventory count of -5 (from conflicting decrements) breaks ordering logic. A user with two profiles breaks authentication.
- •Silent Failures — Perhaps worst: conflicts that resolve 'successfully' but incorrectly, without anyone noticing until the damage is significant.
In Amazon's original Dynamo paper, they describe how shopping cart conflicts were resolved by taking the union of all concurrent cart states. This means if you remove an item from your cart during a conflict, it might reappear—frustrating, but Amazon judged it better than items silently disappearing. This is a conscious trade-off between conflict resolution strategies.
| Domain | Conflict Example | Potential Impact | Acceptable Strategy |
|---|---|---|---|
| E-commerce Cart | Item quantity conflicts | Customer frustration | Union/addition (items reappear) |
| Banking | Concurrent transfers | Financial loss | Strong consistency required |
| Social Media | Concurrent likes/comments | Counter inaccuracy | CRDTs, eventual accuracy |
| Collaborative Docs | Concurrent text edits | Lost changes | Operational transforms |
| Inventory | Concurrent stock updates | Overselling, negative stock | Requires coordination |
| Medical Records | Concurrent updates | Patient safety risk | Strong consistency + audit |
The Conflict Resolution Spectrum
Conflict resolution strategies exist on a spectrum from simple but lossy to complex but precise. Your choice depends on data semantics, consistency requirements, and operational complexity tolerance.
Choosing Your Strategy:
The right conflict resolution strategy depends on multiple factors:
| Factor | Favors Simple (LWW) | Favors Complex (CRDTs) |
|---|---|---|
| Data semantics | Last value is all that matters | History/accumulation matters |
| Conflict frequency | Rare (mostly sequential) | Frequent (high concurrency) |
| Data criticality | Low (logs, metrics) | High (financial, medical) |
| Team expertise | Limited distributed experience | Strong distributed expertise |
| Latency requirements | Can tolerate coordination | Needs low latency always |
Production systems often use hybrid approaches: CRDTs for counters and sets, LWW for simple values, and application-level merge for complex business objects. The key is matching the resolution strategy to the semantic requirements of each data type.
Designing for Conflict Minimization
The best conflict is the one that never happens. While you cannot eliminate all conflicts in a distributed system, thoughtful design significantly reduces their frequency and impact.
- •Partition Data by Access Pattern — If User A only modifies their own profile and User B only modifies theirs, concurrent writes don't conflict. Design data models where writes are naturally isolated to specific users, regions, or entities.
- •Use Conflict-Free Operations — Instead of setting
counter = 5, useincrement counter by 1. Increments from different sources can be safely merged. This is the foundation of CRDTs. - •Reduce Replication Factor for Hot Data — If coordination is possible, fewer replicas mean fewer potential conflict sources. Critical data might use strong consistency even in an eventually consistent system.
- •Time-Bound Write Windows — For some data, only accept writes within a time window. Old writes are rejected, reducing conflict from delayed replication.
- •Immutable/Append-Only Structures — If data is never updated (only appended), conflicts become merge operations. Event sourcing and log-based architectures leverage this principle.
- •Pessimistic Locking for Critical Paths — For truly critical operations (financial transactions), acquire locks across replicas before writing. Accept the latency cost for correctness.
1234567891011121314151617181920212223242526272829
// CONFLICT-PRONE: Updating shared counterinterface ConflictProne { shoppingCart: { totalItems: number; // Concurrent updates conflict! items: Item[]; };} // CONFLICT-RESISTANT: Each user has own partitioninterface ConflictResistant { userCarts: { [userId: string]: { // Only this user modifies their cart items: Item[]; }; };} // CONFLICT-FREE: Operation-based updatesinterface CartOperation { type: 'ADD_ITEM' | 'REMOVE_ITEM' | 'UPDATE_QUANTITY'; itemId: string; userId: string; timestamp: number; delta?: number; // For quantity: +1, -1, etc.} // Operations can be merged: apply all ADDs, then all REMOVEs// Final state is deterministic regardless of operation orderKEEP LEARNING
There’s more to understand.
You’re reading the introduction. Create a free account to read the complete lesson, save notes and keep track of your progress.