---
title: "Conflict-Free Merges"
url: "https://nsta1.github.io/Orleans.Lattice/samples/ConflictFreeMerges/README.html"
source: "https://github.com/NSTA1/Orleans.Lattice/blob/release/9.9/samples/ConflictFreeMerges/README.md"
documents: "Orleans.Lattice 9.9.0 (release line 9.9)"
built: "2026-10-04"
all-pages: "https://nsta1.github.io/Orleans.Lattice/llms.txt"
---
# Conflict-Free Merges

Part of [Samples](../index.md).

## :warning: CRDTs are opt-in

Plain writes on a tree - `SetAsync`, `SetManyAsync`, `SetManyAtomicAsync`, and
friends - are **last-writer-wins** (the `LatticeMergeMode.LwwRegister` merge mode): when two writers touch the
same key concurrently, the later timestamp silently overwrites the earlier one
and the losing update is discarded. That is the correct default for ordinary
key/value data, but it is **not** conflict-free.

To get the convergent, no-lost-update behaviour shown below you must
**explicitly opt in** by writing through one of the typed CRDT extension
accessors (`tree.PnCounter(key)`, `tree.OrSet(key)`, `tree.OrFlag(key)`, ...).
The accessor picks the right merge mode for the key; a plain `SetAsync` to the
same key would fall back to last-writer-wins - or, on a tree declared for
cross-cluster replication under a typed CRDT merge mode, be rejected with
`LatticeReplicationModeMismatchException` before it commits.

## What it shows

Orleans.Lattice can store values as CRDTs, so independent writers can mutate the
same logical value concurrently - with no coordination - and the store always
converges to the **same** final state, regardless of thread interleaving or the
order updates are merged. This is opt-in (see the warning above); this sample
drives it entirely through the typed CRDT **extension** surface on `ILattice`
(`tree.PnCounter(key)`, `tree.OrSet(key)`, ...) rather than instantiating the
primitive types directly.

1. **Convergence under concurrent threads** - 100 writers, each its own replica,
   hammer one shared tree at the same time. Every PN-counter increment and
   OR-set add survives: the final count is exact, with zero lost updates.
2. **Every CRDT type converges regardless of merge order** - two replicas
   diverge in isolation, then exchange and merge each other's state. Each of the
   CRDT accessors the sample exercises reaches an identical result whichever side merges first.

## CRDT types (via the `ILattice` extension surface)

The sample tours the first eight accessors below; the last five complete the
typed CRDT extension surface and are not exercised here.

| Accessor | Semantics | Use case |
|---|---|---|
| `tree.PnCounter(key)` | Increment/decrement counter; merges pointwise-max per replica on its increment and decrement components, and its value is the summed increments minus the summed decrements. | Distributed counters: likes, view counts, inventory reservations. |
| `tree.OrSet(key)` | Observed-remove set; a concurrent add beats a concurrent remove (add-wins). | Membership sets: tags, followers, a shopping cart's contents. |
| `tree.OrFlag(key)` | Enable-wins boolean flag. | A toggle where turning something **on** should win a tie (e.g. activating a feature). |
| `tree.RwFlag(key)` | Disable-wins boolean flag. | A toggle where a **removal** must win a tie: revocation lists, blocklists, opt-outs. |
| `tree.VersionVector(key)` | Causal clock; merges pointwise-max per replica. | Tracking causality - deciding whether two updates are causal or concurrent. |
| `tree.MvRegister<T>(key)` | Multi-value register; concurrent writes survive as a conflict set instead of last-writer-wins. | Conflicting single-value edits the app (or a user) should resolve, e.g. a profile field edited in two places. |
| `tree.OrMap<TKey,TValue>(key)` | Add-wins map whose cells are themselves CRDTs, merged recursively. | Per-entity sub-state: per-user counters, per-city tallies, nested documents. |
| `tree.Sequence<T>(key)` | Replicated growable array; concurrent inserts converge on a deterministic order. | Ordered collaborative data: text buffers, ordered lists, activity feeds. |
| `tree.GCounter(key)` | Grow-only counter; increments only, merges pointwise-max per replica. | Monotone tallies: page views, event counters, quota consumption. |
| `tree.GSet(key)` | Grow-only set; elements can only be added, merges by union. | Append-only sets: seen ids, accumulating tags or audiences. |
| `tree.RwSet(key)` | Remove-wins observed-remove set; a concurrent remove beats a concurrent add. | Membership where a removal must win a tie: revocation lists, blocklists. |
| `tree.MaxRegister<T>(key, orderKeySelector)` | Monotone register that keeps the greatest value under a caller-supplied order key. | High-water marks: a version ceiling, a max-seen reading. |
| `tree.MinRegister<T>(key, orderKeySelector)` | Monotone register that keeps the smallest value under a caller-supplied order key. | Low-water marks: a min-seen latency floor, a first-seen timestamp. |

`tree.OrMap<TKey,TValue>(key)` is the one accessor that needs host registration:
register its `(TKey, TValue)` shape for each tree at silo start with
`siloBuilder.AddOrMapShape<TKey, TValue>(treeName)` - the sample does so for
`replica-a` and `replica-b` - or its writes throw
`LatticeCrdtShapeNotRegisteredException`. Every other accessor resolves its
shape automatically.

## Run it

```
dotnet run --project samples/ConflictFreeMerges
```

## Expected output

```
Silo starting... ready.

== 1. Convergence under concurrent threads ==
  100 writers, each its own replica, all writing at once
  PnCounter 'votes'   = 100 (expected 100)
  OrSet 'members'     = 100 distinct members (expected 100)
  [OK] concurrent writers converged with zero lost updates

== 2. Every CRDT type converges regardless of merge order ==
  Two replicas diverge in isolation, then merge each other's state.

  PnCounter (add/subtract counter)
    after merge: a=7  b=7  [OK -> 7]

  OrSet (add-wins set)
    after merge: a.contains('green')=True  b.contains('green')=True  [add-wins]

  OrFlag (enable-wins flag)
    after merge: a.enabled=True  b.enabled=True  [enable-wins -> True]

  RwFlag (disable-wins flag)
    after merge: a.enabled=False  b.enabled=False  [disable-wins -> False]

  VersionVector (causal version tracker)
    after merge: a.replicas=2  b.replicas=2  identical=True  [both lanes kept]

  MvRegister (multi-value register)
    after merge: a=[left-edit, right-edit]  b=[left-edit, right-edit]  [both edits survive for the app to resolve]

  OrMap (map of CRDT cells)
    after merge: a has london=True paris=True  b has london=True paris=True

  Sequence / RGA (ordered list)
    after merge: a=[b1, a1, a2]  b=[b1, a1, a2]  identical=True  [deterministic order]

Done. Every replica reached the same state without locks, consensus, or a conflict prompt.
```

## When to use

- Multi-writer or multi-region data where you want availability over
  coordination: each writer proceeds locally and the state merges deterministically.
- Counters, sets, flags, registers, maps, and sequences whose concurrent updates
  should combine by algebra (sum, add-wins, disable-wins, max/min, union)
  rather than by a lock or a manual conflict prompt.

## When not to use

- Invariants that require a global check before a write (e.g. "never oversell
  stock"). CRDTs converge, but they do not enforce cross-key transactional
  constraints - use `SetManyAtomicAsync` or an external guard for those.

## Feature docs

[docs/lattice/state-primitives.md](../../docs/lattice/state-primitives.md)
