CRDT Types Guide
Choose the right conflict-free replicated data type for your use case
What are CRDTs?
Conflict-free Replicated Data Types (CRDTs) are data structures that can be replicated across multiple nodes in a distributed system and merged automatically without conflicts. They guarantee that all replicas will eventually converge to the same state, regardless of the order of operations or network partitions.
Convergence
All replicas eventually reach the same state
Conflict-Free
No manual conflict resolution required
Network Partition Tolerant
Works even when nodes are disconnected
Available CRDT Types
Counters
GCounter (Grow-Only Counter)
A counter that can only be incremented. Perfect for tracking metrics that only increase.
Use Cases:
- β’ Event counting (page views, API calls)
- β’ Metrics aggregation
- β’ Resource usage tracking
let mut counter = GCounter::<DefaultConfig>::new(1);
counter.increment()?;
println!("Value: {}", counter.value()); // 1
PNCounter (Increment/Decrement Counter)
A counter that supports both increment and decrement operations using two internal GCounters.
Use Cases:
- β’ Inventory management
- β’ Vote counting (upvotes/downvotes)
- β’ Resource allocation
let mut counter = PNCounter::<DefaultConfig>::new(1);
counter.increment()?;
counter.decrement()?;
println!("Value: {}", counter.value()); // 0
Registers
LWWRegister (Last-Writer-Wins)
Stores a single value with timestamp-based conflict resolution. The most recent write wins.
Use Cases:
- β’ Configuration management
- β’ User profile data
- β’ System status tracking
let mut reg = LWWRegister::<&str, DefaultConfig>::new(1);
reg.set("value", 1000)?;
println!("Value: {:?}", reg.get()); // Some("value")
MVRegister (Multi-Value Register)
Stores multiple concurrent values when conflicts occur, allowing application-level resolution.
Use Cases:
- β’ Collaborative editing
- β’ Sensor calibration
- β’ Conflict detection systems
let mut reg = MVRegister::<f32, DefaultConfig>::new(1);
reg.set(1.05, 1000)?;
let values: Vec<f32> = reg.values().cloned().collect();
Sets
GSet (Grow-Only Set)
A set that only supports adding elements. Once added, elements cannot be removed.
Use Cases:
- β’ Device capability registry
- β’ Feature flags
- β’ Permanent audit logs
let mut set = GSet::<u32, DefaultConfig>::new();
set.insert(42)?;
println!("Contains 42: {}", set.contains(&42)); // true
ORSet (Observed-Remove Set)
A set that supports both adding and removing elements using unique tags for each operation.
Use Cases:
- β’ Shopping cart management
- β’ Active user sessions
- β’ Dynamic group membership
let mut set = ORSet::<u32, DefaultConfig>::new(1);
let tag = set.insert(42)?;
set.remove(&42, tag)?;
Maps
LWWMap (Last-Writer-Wins Map)
A key-value map where each key uses last-writer-wins semantics for conflict resolution. Supports insert, update, remove, and query operations.
Use Cases:
- β’ Distributed configuration stores
- β’ Sensor data aggregation
- β’ User preference management
- β’ Metadata storage
- β’ Device state management
Key Operations:
Add or update a key-value pair
Remove a key and return its value
Retrieve the value for a key
Merge with another LWWMap
Basic Usage:
use crdtosphere::prelude::*;
let mut map = LWWMap::<&str, f32, DefaultConfig>::new(1);
// Insert values with timestamps
map.insert("temperature", 25.5, 1000)?;
map.insert("humidity", 60.0, 1001)?;
// Query values
println!("Temp: {:?}", map.get(&"temperature"));
println!("Map length: {}", map.len());
Remove Operations:
// Remove a key and get its value
let removed_value = map.remove(&"humidity");
println!("Removed: {:?}", removed_value); // Some(60.0)
// Check if key still exists
println!("Contains humidity: {}", map.contains_key(&"humidity")); // false
// Capacity is freed for new entries
println!("Remaining capacity: {}", map.remaining_capacity());
Conflict Resolution:
let mut map1 = LWWMap::<&str, i32, DefaultConfig>::new(1);
let mut map2 = LWWMap::<&str, i32, DefaultConfig>::new(2);
// Both nodes update the same key
map1.insert("counter", 10, 1000)?;
map2.insert("counter", 20, 2000)?; // Newer timestamp
// Merge maps - newer timestamp wins
map1.merge(&map2)?;
println!("Final value: {:?}", map1.get(&"counter")); // Some(20)
Atomic Version (Thread-Safe):
// Enable hardware-atomic feature for thread-safe operations - std is for demonstration.
use std::sync::Arc;
use std::thread;
let map = Arc::new(LWWMap::<&str, i32, DefaultConfig>::new(1));
// Share map between threads
let map_clone = Arc::clone(&map);
let handle = thread::spawn(move || {
map_clone.insert("shared_data", 42, 1000)
});
handle.join().unwrap();
CRDT Selection Guide
Choose Based on Operations
Need to count things?
Use GCounter for increment-only or PNCounter for increment/decrement
Need to store single values?
Use LWWRegister for simple cases or MVRegister for conflict detection
Need to manage collections?
Use GSet for add-only or ORSet for add/remove operations
Need key-value storage?
Use LWWMap for distributed key-value data
Choose Based on Use Case
π Automotive
LWWRegister for sensor data, GCounter for error counts
π€ Robotics
GSet for robot discovery, LWWMap for position tracking
π IoT
LWWMap for sensor readings, GSet for device capabilities
π Industrial
GCounter for production counts, LWWRegister for equipment status