---
title: "Version Vector"
url: "https://nsta1.github.io/Orleans.Lattice/docs/crdt/versionvector.html"
source: "https://github.com/NSTA1/Orleans.Lattice/blob/release/9.9/docs/crdt/versionvector.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"
bundle: "https://nsta1.github.io/Orleans.Lattice/docs/crdt/llms-full.txt"
---
# Version Vector

Part of the [CRDTs documentation](readme.md).

`tree.VersionVector(key)` -> `VersionVectorAccessor`, merge mode `LatticeMergeMode.VersionVector`.

## Semantics

A **version vector** does not store application data - it stores **causal
history**: one logical clock per replica, answering "how far has each replica
progressed, and has A seen everything B has?".

Each replica **ticks** its own entry when it does something noteworthy. Merging
takes the **per-entry maximum** clock. Comparing two vectors then tells you
whether one causally dominates the other (`DominatesOrEquals`) or whether they
are **concurrent** (each is ahead of the other on at least one entry) - the classic signal that
two updates conflicted and need reconciliation.

Because merge is per-entry max, late or duplicate delivery is a harmless no-op.

Use it for: detecting concurrent edits, driving anti-entropy / "who is behind"
decisions, and building higher-level conflict detection on top of raw values.

## Behaviour

Figure: Two concurrent version-vector updates meeting at their join. An order diagram shaped like a diamond. At the bottom is a history both replicas share, A:1 and B:1. Cluster A ticks its own entry, reaching A:2, B:1. Cluster B ticks its own twice, reaching A:1, B:3. Neither vector dominates the other: each has an entry the other lacks. Merging takes the per-entry maximum, so both paths rise to the same top state, A:2, B:3.

Both clusters hold A:2, B:3. A had A:2 > 1 and B had B:3 > 1, so neither dominated: the updates were concurrent and need reconciling.

Merge takes the per-entry maximum, so late or duplicate delivery is a harmless no-op.

```mermaid
graph TD
    A["A: { A:2, B:1 }"] -->|merge = per-entry max| M["{ A:2, B:3 }"]
    B["B: { A:1, B:3 }"] -->|merge = per-entry max| M
    M --> Q{"A dominates B?"}
    Q -->|"A had A:2 > 1, B had B:3 > 1"| C["concurrent -> reconcile"]
```

## Example

```csharp verify
var history = tree.VersionVector("doc:12:history");

// Each replica ticks its own entry as it makes edits.
await history.TickAsync("replica-A", cancellationToken);
await history.TickAsync("replica-A", cancellationToken);
await history.TickAsync("replica-B", cancellationToken);

// Read the merged causal frontier and compare against another vector to
// decide whether two states are concurrent or causally ordered.
VersionVector current = await history.GetAsync(cancellationToken);
var other = new VersionVector();
other.Tick("replica-C");
bool seenEverythingInOther = current.DominatesOrEquals(other); // false: replica-C's entry is unseen
```

See also: [MV-Register](mvregister.md), which tags each value with a causal dot
and remembers the dots each write has observed - the same concurrency-detection
idea at the single-value level - and the [CRDT overview](readme.md).
