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.

CRDTATOMiKNotes
CRDT StateAtomikContextBoth 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 PayloadDelta (XOR diff)CRDTs transmit full state or operation. ATOMiK transmits the XOR of what changed -- typically much smaller.
Causality metadataNot neededVector clocks, version vectors, dot stores -- ATOMiK needs none. Commutativity + associativity + idempotency from self-inverse.
Conflict resolutionAlgebraic convergenceCRDTs 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.timestamp

After: 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) merge

After: 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 convergence

Performance Comparison

Where CRDTs incur per-node or per-operation overhead for causality tracking, ATOMiK operates with constant-time, constant-space accumulation.

DimensionCRDTsATOMiK
Merge complexityO(n) -- depends on state sizeO(1) -- single XOR operation
Metadata overheadVector clocks: O(nodes), Dot stores: O(ops)Zero -- no causality metadata
Sync bandwidthFull state (state-based) or op + context (op-based)8-byte delta only
State reconstructionO(1) if materialized, O(n) if from opsO(1) always -- initial XOR accumulator
Undo / rollbackNot supported (lattice is monotonic)Free -- apply same delta again (self-inverse)
Formal guaranteesSEC (Strong Eventual Consistency)SEC + self-inverse + 92 Lean4 proofs
Implementation complexityHigh -- many CRDT types, each with edge casesMinimal -- 4 operations, one algebraic structure

Migration Checklist

1

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
2

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
3

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)
4

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.