system-design · intermediate

Active-Active Conflict Resolution — CRDTs and Vector Clocks

The Central Question

Consider a multi-region collaborative document and user profile platform running on the TxLab platform (txlab.com) processing 100,000,000 queries per day:


Because both database nodes accept writes independently before replicating across the Atlantic Ocean, a Write Conflict occurs.

If the system naively applies the write that arrives second, network jitter could cause older updates to overwrite newer updates, or cause US and EU database nodes to diverge permanently.

This condition is an Active-Active Write Conflict.

This lesson answers one central question: How do Active-Active multi-master databases detect and resolve concurrent write conflicts across regions, and how do engineers compare Last-Write-Wins (LWW), Vector Clocks, and Conflict-Free Replicated Data Types (CRDTs) to achieve deterministic eventual convergence?


The Active-Active Multi-Master Topology

In Active-Active database architectures, every regional data center hosts a primary database node capable of accepting read and write transactions locally:

flowchart TD
  subgraph Region US-East Datacenter
    ClientUS[Client US] -->|1. Local Write: status = 'Working'| PrimaryUS[(Primary US DB)]
  end

subgraph Region EU-West Datacenter
ClientEU[Client EU] -->|1. Local Write: status = 'Transit'| PrimaryEU[(Primary EU DB)]
end

PrimaryUS <-->|2. Cross-Ocean Async Replication Stream| PrimaryEU

Note over PrimaryUS,PrimaryEU: WRITE CONFLICT! Both nodes modified 'user:101' concurrently!

Figure 1: Active-Active multi-master topology allowing concurrent local writes across regions.


Conflict Resolution Strategies: LWW, Vector Clocks, and CRDTs

Databases deploy three principal mechanisms to resolve concurrent write conflicts:

flowchart TD
  Strategies[Active-Active Conflict Resolution] --> LWW[1. Last-Write-Wins LWW]
  Strategies --> VC[2. Vector Clocks / Version Vectors]
  Strategies --> CRDT[3. Conflict-Free Replicated Data Types CRDT]
  
  LWW --> LWWDesc["Uses physical NTP timestamp.<br/>Highest timestamp wins; risks silent data loss due to clock skew."]
  VC --> VCDesc["Tracks causal history per node [US:2, EU:1].<br/>Detects true concurrency and prompts application merge."]
  CRDT --> CRDTDesc["Mathematical data structures (PN-Counters, LWW-Element-Set).<br/>Guarantees deterministic convergence without locks."]

Figure 2: Taxonomy of active-active conflict resolution strategies.


Strategy 1: Last-Write-Wins (LWW) & The Clock Skew Risk

The simplest conflict resolution strategy is Last-Write-Wins (LWW):


The Fatal Vulnerability: Clock Skew Data Loss


Physical server clocks synchronized via Network Time Protocol (NTP) experience Clock Skew (typically 5ms to 50ms drift across datacenters).

If Server US's physical clock is 20ms ahead of Server EU's clock, a write executed on Server EU at 12:00:00.050 will be silently overwritten and discarded by an earlier write executed on Server US at 12:00:00.040, causing silent data loss!


Strategy 2: Vector Clocks (Causal History Tracking)

To detect true concurrency without relying on physical NTP clock synchronization, systems use Vector Clocks:


flowchart LR
subgraph Causal Ordering vs Concurrency
V1["Vector A: [US:1, EU:0]"] -->|Causally Precedes| V2["Vector B: [US:2, EU:0]"]

V3["Vector C: [US:2, EU:0]"] <-->|CONCURRENT CONFLICT!| V4["Vector D: [US:1, EU:1]"]
end

Figure 3: Comparing causally dominant vector clocks against concurrent conflicting vectors.

If Vector A has higher or equal counters than Vector B across all entries, Vector A causally succeeds Vector B (overwrite is safe). If neither vector dominates, a True Concurrent Conflict is flagged, prompting application code or user intervention to merge.


Strategy 3: Conflict-Free Replicated Data Types (CRDTs)

For complex data structures (such as shopping carts, online user counters, or collaborative text documents), systems deploy Conflict-Free Replicated Data Types (CRDTs).

CRDTs are mathematically designed data structures that can be updated independently across multiple regional nodes without central coordination, guaranteeing that as soon as all nodes receive the same set of updates, they automatically converge to the exact same state.

Mathematical Foundations of CRDTs

A state-based CRDT merge function ($\sqcup$) must satisfy three mathematical properties:
  1. Commutative: $A \sqcup B = B \sqcup A$ (Order of update arrival does not matter).
  2. Associative: $(A \sqcup B) \sqcup C = A \sqcup (B \sqcup C)$ (Grouping of network packets does not matter).
  3. Idempotent: $A \sqcup A = A$ (Duplicate network packet delivery does not alter state).

State-Based (CvRDT) vs Operation-Based (CmRDT)

Vector Clock Garbage Collection & Pruning

Because vector clocks track an entry for every node that ever modified a key, in dynamic cloud environments where nodes autoscale up and down, vector clocks grow continuously (**Vector Clock Bloat**). Distributed databases enforce **Vector Clock Pruning**:

LWW-Element-Set Removal Bias vs Add Bias

When building CRDT sets (such as user shopping carts), concurrent add and remove operations (`add(item_A)` at $T_1$ vs `remove(item_A)` at $T_1$) require explicit tie-breaking rules:

CRDT State Compression

To prevent tombstone markers in LWW-Element-Sets from consuming excessive disk space, background compaction daemons compress dead tombstone elements after $T_{\text{tombstone}}$ retention windows pass across all replicas.

Complete Worked Example: Go CRDT Conflict Resolver (PN-Counter & LWW-Set)

Let's inspect a complete Go implementation of a Conflict-Free Replicated Data Type (P-N Counter and LWW-Element-Set) for the TxLab platform (txlab.com).

package main

import (
"fmt"
"sync"
)

// PNCounter is a State-Based Positive-Negative Counter CRDT
type PNCounter struct {
mu sync.RWMutex
nodeID string
posCounts map[string]int64
negCounts map[string]int64
}

func NewPNCounter(nodeID string) *PNCounter {
return &PNCounter{
nodeID: nodeID,
posCounts: make(map[string]int64),
negCounts: make(map[string]int64),
}
}

func (c *PNCounter) Increment(amount int64) {
c.mu.Lock()
defer c.mu.Unlock()
c.posCounts[c.nodeID] += amount
}

func (c *PNCounter) Decrement(amount int64) {
c.mu.Lock()
defer c.mu.Unlock()
c.negCounts[c.nodeID] += amount
}

func (c *PNCounter) Value() int64 {
c.mu.RLock()
defer c.mu.RUnlock()

var total int64
for _, v := range c.posCounts {
total += v
}
for _, v := range c.negCounts {
total -= v
}
return total
}

func (c PNCounter) Merge(other PNCounter) {
c.mu.Lock()
defer c.mu.Unlock()
other.mu.RLock()
defer other.mu.RUnlock()

// Merge Positive Counts (Take Max per node)
for k, v := range other.posCounts {
if v > c.posCounts[k] {
c.posCounts[k] = v
}
}

// Merge Negative Counts (Take Max per node)
for k, v := range other.negCounts {
if v > c.negCounts[k] {
c.negCounts[k] = v
}
}
fmt.Printf("[CRDT MERGE] Nodes merged cleanly. Consolidated Value: %d\n", c.Value())
}


Failure Modes and Engineering Mitigations

Failure ScenarioRoot CauseSystem SymptomDetection SignalMitigation Strategy
1. LWW Clock Skew Silent Data LossServer US physical clock is 50ms ahead of Server EU; writes on EU are discarded.Users report recently edited bio or settings reverting to old states.High physical clock drift metrics on NTP synchronization daemons.Replace LWW with Vector Clocks or CRDTs for critical data paths.
2. Vector Clock Memory ExplosionMulti-master cluster spawns 10,000 ephemeral client nodes; vector clocks grow to 10 MB per row.Database storage usage explodes; network bandwidth saturates.High row byte size metrics on vector clock metadata attributes.Apply Vector Clock Pruning (Garbage Collection) for inactive nodes.
3. CRDT Shopping Cart Item ReappearanceUser removes item from cart in EU; concurrent add in US causes item to reappear post-merge.Customer frustration due to deleted items staying in online shopping carts.Customer support tickets reporting undeletable shopping cart items.Use Observed-Remove Sets (OR-Set) instead of LWW-Element-Sets.
4. Unbounded Multi-Master DivergenceCross-region WAN link stays down for 3 days; CDC delta queues fill database disk space.Storage disk hits 100% capacity; multi-master replication halts completely.Cross-region replication queue lag metrics exceeding 24 hours.Enforce Backpressure Throttling and provision adequate replication disk buffers.

What You Should Remember

  1. Active-Active enables multi-region local writes: Allow clients in US and EU to write locally for sub-10ms response times, but prepare for write conflicts.
  2. LWW is simple but vulnerable to clock skew: Last-Write-Wins discards older timestamps, risking silent data loss when physical server clocks drift.
  3. Vector Clocks track causal history: Use arrays of logical counters per node to detect true concurrent conflicts without relying on physical NTP clocks.
  4. CRDTs guarantee deterministic mathematical convergence: Use CRDTs (PN-Counters, LWW-Sets, OR-Sets) to merge divergent replica states automatically without locks.
  5. CRDT merge functions must be Commutative, Associative, and Idempotent: Ensure merge functions ($A \sqcup B$) handle out-of-order and duplicate network packet arrivals cleanly.

Glossary of Terms

TermDefinition
Active-Active ReplicationA multi-master database topology where multiple regional nodes accept reads and writes concurrently.
Write ConflictAn anomaly occurring when two different database nodes modify the same data record concurrently.
Last-Write-Wins (LWW)A conflict resolution heuristic preserving the write holding the highest physical wall-clock timestamp.
Vector ClockAn array of logical counters tracking causal dependency relationships between distributed nodes.
CRDTConflict-Free Replicated Data Type; a mathematical data structure that converges deterministically across nodes.
Clock SkewThe time difference between physical wall clocks on independent server nodes.

Practice Scenario and Self-Assessment

Architecture Scenario

You are designing the collaborative document editing engine for an enterprise platform (`docs.txlab.com`): **Questions**:
  1. Formulate the exact CRDT data structure selection (Sequence CRDT / Ropes vs LWW-Set) for real-time document editing.
  2. Design the vector clock pruning mechanism to prevent metadata bloat as millions of clients join and leave document sessions.

Interactive Self-Assessment

Physical server clocks experience Clock Skew across regions, causing writes on lagging clocks to receive older timestamps and be silently discarded.

LWW causes physical hardware electrical short-circuits in server power units.

LWW revokes client HTTPS TLS encryption certificates on edge load balancers.

LWW doubles the physical hardware clock speed of primary database CPUs.

Commutative (order doesn't matter), Associative (grouping doesn't matter), and Idempotent (duplicates don't matter).

CRDT merge functions automatically convert relational database primary key indexes into un-indexed CSV files.

CRDT merge functions replace public DNS nameservers with local hosts file entries.

CRDT merge functions reboot operating system hypervisors across all cluster nodes.


What to Learn Next

Track: Staff+ Technical Leadership

Previous: Multi-Region Failover — Surviving a Region Outage

Next: Payment Platform Architecture

By Shubham Jain

All articles · Study paths

Shubham Jain · Learning Lab