Migration Guide
Migrate from CRDTs to ATOMiK
CRDTs and ATOMiK both solve distributed convergence without coordination. This guide maps CRDT concepts to ATOMiK operations, shows before/after code for three common patterns, and provides a step-by-step migration checklist.
When to migrate (and when not to)
Migrate when:
- ✓Your CRDT merge is a performance bottleneck (O(n) state merges)
- ✓Causality metadata (vector clocks, dot stores) dominates bandwidth
- ✓You need undo/rollback (impossible with monotonic CRDTs)
- ✓Your data is numeric or binary (counters, flags, fingerprints)
- ✓You want formal verification (92 Lean4 proofs vs informal CRDT specs)
Stay with CRDTs when:
- ✗You need rich set semantics (OR-Set add/remove with tombstones)
- ✗Your data requires causal ordering guarantees between operations
- ✗You use collaborative text editing (CRDTs like Yjs/Automerge excel here)
- ✗Your CRDT library already meets performance requirements
Conceptual Mapping
CRDTs use a join-semilattice. ATOMiK uses an Abelian group. The algebraic structure is different, but the convergence guarantee is the same -- any permutation of operations produces the same final state.
| CRDT | ATOMiK | Notes |
|---|---|---|
| CRDT State | AtomikContext | Both represent a convergent replicated value. ATOMiK separates reference state from accumulated deltas. |
| merge(a, b) | ctx.accum(delta) | CRDT merge is a join on the semilattice. ATOMiK accumulation is XOR in an Abelian group. Both are commutative. |
| CRDT Payload | Delta (XOR diff) | CRDTs transmit full state or operation. ATOMiK transmits the XOR of what changed -- typically much smaller. |
| Causality metadata | Not needed | Vector clocks, version vectors, dot stores -- ATOMiK needs none. Commutativity + associativity + idempotency from self-inverse. |
| Conflict resolution | Algebraic convergence | CRDTs resolve via lattice join (LWW, OR-Set, etc). ATOMiK converges via Abelian group -- no conflicts possible. |
| query() | ctx.read() | Both return current value. CRDT queries the lattice state. ATOMiK reconstructs: initial_state XOR accumulator. |
Pattern 1: G-Counter / PN-Counter
CRDTs use separate increment/decrement maps per node, merged with max(). ATOMiK uses a single accumulator with self-inverse deltas.
Before: CRDT PN-Counter
# Each node maintains its own counter vector
class PNCounter:
def __init__(self, node_id, num_nodes):
self.p = [0] * num_nodes
self.n = [0] * num_nodes
self.id = node_id
def increment(self): self.p[self.id] += 1
def decrement(self): self.n[self.id] += 1
def value(self): return sum(self.p) - sum(self.n)
def merge(self, other):
# O(n) merge per node
for i in range(len(self.p)):
self.p[i] = max(self.p[i], other.p[i])
self.n[i] = max(self.n[i], other.n[i])After: ATOMiK
from atomik_core import AtomikContext
# Single context, any node can accumulate
counter = AtomikContext()
counter.load(0)
# Any node: accumulate a delta
counter.accum(delta_value)
# Read: O(1), no merge needed
current = counter.read()
# Undo: apply same delta again
counter.accum(delta_value) # self-inverse
# Sync: send 8-byte delta, not full state
# No node IDs. No vector clocks.
# No O(n) merge. Just XOR.Pattern 2: LWW-Register
Last-Writer-Wins registers require synchronized clocks or hybrid logical clocks. ATOMiK eliminates the timestamp dependency entirely -- convergence is algebraic, not temporal.
Before: LWW-Register
class LWWRegister:
def __init__(self):
self.value = None
self.timestamp = 0
def set(self, val, ts):
if ts > self.timestamp:
self.value = val
self.timestamp = ts
def merge(self, other):
# Requires synchronized clocks!
if other.timestamp > self.timestamp:
self.value = other.value
self.timestamp = other.timestampAfter: ATOMiK
from atomik_core import AtomikContext
reg = AtomikContext()
reg.load(initial_value)
# Update: compute delta from current state
delta = new_value ^ reg.read()
reg.accum(delta)
# No timestamps. No clock sync.
# Convergence is algebraic:
# all replicas accumulating the same
# deltas reach the same state.Pattern 3: Distributed State Sync
Multi-node state synchronization using CRDTs requires transmitting full state or operation logs with causal metadata. ATOMiK transmits only the delta and converges regardless of delivery order.
Before: State-based CRDT Sync
# Node A sends full state to Node B
def sync_to_peer(local_crdt, peer):
# Serialize entire CRDT state
payload = local_crdt.serialize()
# Includes: values + vector clock
# + tombstones + node metadata
peer.send(payload) # O(state_size)
def on_receive(local_crdt, payload):
remote = CRDT.deserialize(payload)
local_crdt.merge(remote) # O(n) mergeAfter: ATOMiK Delta Sync
# Node A sends 8-byte delta to Node B
def sync_to_peer(ctx, peer, delta):
peer.send(delta) # 8 bytes, always
def on_receive(ctx, delta):
ctx.accum(delta) # O(1), done
# No serialization overhead
# No vector clocks
# No tombstone garbage collection
# No merge conflicts
# Order-independent convergencePerformance Comparison
Where CRDTs incur per-node or per-operation overhead for causality tracking, ATOMiK operates with constant-time, constant-space accumulation.
| Dimension | CRDTs | ATOMiK |
|---|---|---|
| Merge complexity | O(n) -- depends on state size | O(1) -- single XOR operation |
| Metadata overhead | Vector clocks: O(nodes), Dot stores: O(ops) | Zero -- no causality metadata |
| Sync bandwidth | Full state (state-based) or op + context (op-based) | 8-byte delta only |
| State reconstruction | O(1) if materialized, O(n) if from ops | O(1) always -- initial XOR accumulator |
| Undo / rollback | Not supported (lattice is monotonic) | Free -- apply same delta again (self-inverse) |
| Formal guarantees | SEC (Strong Eventual Consistency) | SEC + self-inverse + 92 Lean4 proofs |
| Implementation complexity | High -- many CRDT types, each with edge cases | Minimal -- 4 operations, one algebraic structure |
Migration Checklist
Assessment
- Inventory all CRDT types in use (G-Counter, PN-Counter, OR-Set, LWW-Register, etc.)
- Identify which CRDTs rely on monotonic growth (ATOMiK supports non-monotonic via self-inverse)
- Map causality metadata dependencies -- if you need causal ordering, ATOMiK alone may not suffice
- Benchmark current merge latency and sync bandwidth as a baseline
Prototype
- Install atomik-core: pip install atomik-core
- Replace one simple CRDT (G-Counter or LWW-Register) with AtomikContext
- Validate convergence: feed the same deltas in different orders, confirm identical read()
- Measure bandwidth reduction: compare CRDT sync payload vs 8-byte ATOMiK delta
Integration
- Replace CRDT merge logic with accum() calls in your replication layer
- Remove vector clock / dot store metadata from your wire protocol
- Update serialization: deltas are fixed-size integers, not structured CRDT payloads
- Implement self-inverse undo if your application needs rollback (was impossible with CRDTs)
Validation
- Run convergence tests: all replicas must reach identical state regardless of delta order
- Verify self-inverse property: double-applying any delta returns to previous state
- Load test: confirm O(1) merge performance holds under production traffic
- Monitor bandwidth: measure actual reduction vs CRDT baseline
The algebraic difference
CRDTs form a join-semilattice: merge is commutative, associative, and idempotent, but not invertible. State can only grow. ATOMiK forms an Abelian group: merge (XOR) is commutative, associative, has an identity element, and every element is its own inverse. This gives you free undo, constant-size state, and zero metadata overhead -- at the cost of lattice monotonicity. If your application needs monotonic convergence (e.g., grow-only sets), CRDTs remain the right choice.