Conflict-free Replicated Data Types (CRDTs) fundamentals including convergence, commutativity, and basic CRDT operations
Scope: CRDTs, eventual consistency, conflict-free merging, convergence properties Lines: ~320 Last Updated: 2025-10-27 Format Version: 1.0 (Atomic)
Activate this skill when:
CRDT: Conflict-free Replicated Data Type
Key property: Replicas can be updated independently and concurrently, and are guaranteed to converge to same state
Replica A: value = 1 → value += 5 → value = 6 ─┐
├→ Merge → value = 9
Replica B: value = 1 → value += 3 → value = 4 ─┘
Without coordination, replicas converge!
Benefits:
Guarantee: Replicas that have received same updates are in same state
Requirements:
Approach: Broadcast operations, apply in order
Requirements:
Example: G-Counter (grow-only counter)
class GCounter:
"""Operation-based grow-only counter"""
def __init__(self, replica_id, n_replicas):
self.replica_id = replica_id
self.counts = [0] * n_replicas # One count per replica
def increment(self):
"""Increment local counter"""
self.counts[self.replica_id] += 1
return ('increment', self.replica_id) # Operation to broadcast
def apply_operation(self, operation):
"""Apply received operation"""
op_type, replica_id = operation
if op_type == 'increment':
self.counts[replica_id] += 1
def value(self):
"""Get total count"""
return sum(self.counts)
Approach: Periodically merge entire state
Requirements:
Example: G-Counter (state-based)
class GCounterState:
"""State-based grow-only counter"""
def __init__(self, replica_id, n_replicas):
self.replica_id = replica_id
self.counts = [0] * n_replicas
def increment(self):
"""Increment local counter"""
self.counts[self.replica_id] += 1
def merge(self, other):
"""Merge with another replica (element-wise max)"""
new_counts = [
max(self.counts[i], other.counts[i])
for i in range(len(self.counts))
]
result = GCounterState(self.replica_id, len(self.counts))
result.counts = new_counts
return result
def value(self):
"""Get total count"""
return sum(self.counts)
# Usage
replica_a = GCounterState(0, 3)
replica_b = GCounterState(1, 3)
replica_a.increment()
replica_a.increment()
replica_b.increment()
# Merge (commutative, idempotent)
merged = replica_a.merge(replica_b)
print(merged.value()) # 3
Operations: Increment only
Properties:
Use cases: Page views, likes, metrics
Idea: Two G-Counters (positive and negative)
class PNCounter:
"""Counter that can increment and decrement"""
def __init__(self, replica_id, n_replicas):
self.replica_id = replica_id
self.positive = GCounterState(replica_id, n_replicas)
self.negative = GCounterState(replica_id, n_replicas)
def increment(self):
self.positive.increment()
def decrement(self):
self.negative.increment()
def value(self):
return self.positive.value() - self.negative.value()
def merge(self, other):
result = PNCounter(self.replica_id, len(self.positive.counts))
result.positive = self.positive.merge(other.positive)
result.negative = self.negative.merge(other.negative)
return result
class GSet:
"""Grow-only set (add-only)"""
def __init__(self):
self.elements = set()
def add(self, element):
self.elements.add(element)
def lookup(self, element):
return element in self.elements
def merge(self, other):
"""Union of sets"""
result = GSet()
result.elements = self.elements | other.elements
return result
Idea: Two G-Sets (added and removed)
class TwoPhaseSet:
"""Add and remove, but can't re-add after remove"""
def __init__(self):
self.added = set()
self.removed = set()
def add(self, element):
self.added.add(element)
def remove(self, element):
if element in self.added:
self.removed.add(element)
def lookup(self, element):
return element in self.added and element not in self.removed
def merge(self, other):
result = TwoPhaseSet()
result.added = self.added | other.added
result.removed = self.removed | other.removed
return result
import time
from typing import Dict
class DistributedCounter:
"""Production-ready distributed counter using PN-Counter CRDT"""
def __init__(self, replica_id: str, all_replicas: list):
self.replica_id = replica_id
self.all_replicas = all_replicas
self.counter = PNCounter(replica_id, len(all_replicas))
def increment(self, amount: int = 1):
"""Increment counter locally"""
for _ in range(amount):
self.counter.increment()
def decrement(self, amount: int = 1):
"""Decrement counter locally"""
for _ in range(amount):
self.counter.decrement()
def get_value(self) -> int:
"""Get current counter value"""
return self.counter.value()
def sync_with_replica(self, other_replica):
"""Sync state with another replica"""
self.counter = self.counter.merge(other_replica.counter)
def periodic_sync(self, interval: int = 5):
"""Periodically sync with all replicas"""
while True:
for replica in self.all_replicas:
if replica != self:
self.sync_with_replica(replica)
time.sleep(interval)
# Usage
replicas = [
DistributedCounter('A', []),
DistributedCounter('B', []),
DistributedCounter('C', [])
]
# Each replica has reference to others
for r in replicas:
r.all_replicas = replicas
# Concurrent updates
replicas[0].increment(5)
replicas[1].increment(3)
replicas[2].decrement(2)
# Sync
for i in range(len(replicas)):
for j in range(i + 1, len(replicas)):
replicas[i].sync_with_replica(replicas[j])
# All replicas converge to same value
assert replicas[0].get_value() == replicas[1].get_value() == replicas[2].get_value()
Property: All replicas eventually reach same state
How:
Merge must be:
1. Commutative: merge(A, B) = merge(B, A)
2. Idempotent: merge(A, A) = A
3. Associative: merge(merge(A, B), C) = merge(A, merge(B, C))
Test:
def test_convergence():
"""Test that replicas converge regardless of merge order"""
r1 = GCounterState(0, 2)
r2 = GCounterState(1, 2)
r1.increment()
r1.increment()
r2.increment()
# Merge in different orders
result1 = r1.merge(r2)
result2 = r2.merge(r1)
assert result1.value() == result2.value() # Convergence
Property: State only grows, never shrinks (in terms of information)
Implication: Can't truly "delete" - must use tombstones
✅ No coordination needed
✅ Always available
✅ Partition tolerant
✅ Low latency (local operations)
✅ Offline-first friendly
❌ Limited operations (must be commutative)
❌ Metadata overhead (tracking causality)
❌ Some operations complex (e.g., remove from set)
❌ Conflicts resolved automatically (may not match user intent)
❌ Growing state size (garbage collection needed)
✅ Collaborative editing (Google Docs, Figma)
✅ Distributed databases (Riak, Redis)
✅ Shopping cart
✅ Presence indicators (online/offline)
✅ Like counters
✅ Distributed caching
❌ Financial transactions (need strong consistency)
❌ Inventory management (can't oversell)
❌ Sequential operations (order matters)
❌ Complex business logic
| Approach | Consistency | Availability | Coordination | Latency | |----------|------------|--------------|--------------|---------| | CRDTs | Eventual | High | None | Low | | Consensus (RAFT) | Strong | Medium | High | Medium | | 2PC | Strong | Low | High | High | | Last-Write-Wins | Weak | High | None | Low |
# Redis CRDT support (Redis Enterprise)
import redis
r = redis.Redis()
# CRDT Counter
r.execute_command('CRDT.COUNTER', 'mykey', 'INC', 5)
# CRDT Set
r.execute_command('CRDT.ORADD', 'myset', 'element')
# Riak KV with CRDTs
from riak import RiakClient
client = RiakClient()
bucket = client.bucket_type('maps').bucket('my_bucket')
# Riak Map CRDT
my_map = bucket.new()
my_map.counters['page_views'].increment(1)
my_map.sets['tags'].add('crdt')
my_map.store()
import unittest
class TestCRDT(unittest.TestCase):
def test_commutativity(self):
"""Test merge is commutative"""
r1 = GCounterState(0, 2)
r2 = GCounterState(1, 2)
r1.increment()
r2.increment()
r2.increment()
# merge(A, B) == merge(B, A)
result1 = r1.merge(r2)
result2 = r2.merge(r1)
self.assertEqual(result1.value(), result2.value())
def test_idempotence(self):
"""Test merge is idempotent"""
r1 = GCounterState(0, 2)
r1.increment()
# merge(A, A) == A
result = r1.merge(r1)
self.assertEqual(result.value(), r1.value())
def test_associativity(self):
"""Test merge is associative"""
r1 = GCounterState(0, 3)
r2 = GCounterState(1, 3)
r3 = GCounterState(2, 3)
r1.increment()
r2.increment()
r3.increment()
# merge(merge(A, B), C) == merge(A, merge(B, C))
result1 = r1.merge(r2).merge(r3)
result2 = r1.merge(r2.merge(r3))
self.assertEqual(result1.value(), result2.value())
Location: skills/distributed-systems/crdt-fundamentals/resources/
REFERENCE.md (~950 lines): Comprehensive CRDT reference covering:
simulate_crdt.py: Simulate CRDT operations with concurrent replicas
# Simulate G-Counter with 3 replicas
./simulate_crdt.py g-counter --replicas 3
# Simulate OR-Set with concurrent add/remove
./simulate_crdt.py or-set --scenario concurrent-ops --json
# Simulate RGA text editing
./simulate_crdt.py rga --scenario text-edit
benchmark_merge.py: Benchmark CRDT merge performance
# Benchmark all CRDTs
./benchmark_merge.py all --benchmark all
# Benchmark specific CRDT with custom sizes
./benchmark_merge.py or-set --benchmark merge-scaling --sizes 10,100,1000
# JSON output for visualization
./benchmark_merge.py pn-counter --json > results.json
visualize_convergence.py: Generate convergence diagrams
# ASCII visualization of linear convergence
./visualize_convergence.py g-counter --scenario linear
# Mermaid diagram for star topology
./visualize_convergence.py or-set --scenario star --format mermaid
# Export all formats
./visualize_convergence.py g-counter --scenario partition --format all -o convergence.md
Python:
g_counter.py: G-Counter with serialization, distributed system simulationor_set.py: OR-Set with add-wins semantics, shopping cart examplelww_register.py: LWW-Register with hybrid logical clocks, MV-RegisterTypeScript:
yjs-collaborative-editing.ts: Real-time collaborative text editing with YjsJavaScript:
automerge-example.js: Automerge for JSON CRDTsAll examples are runnable and include multiple scenarios demonstrating:
distributed-systems-crdt-types - Specific CRDT implementationsdistributed-systems-eventual-consistency - Consistency modelsdistributed-systems-conflict-resolution - Conflict handlingdistributed-systems-vector-clocks - Causality trackingLast Updated: 2025-10-27