Table of Contents

OR-Set (Observed-Remove Set)

This page is part of the documentation for Orleans.Lattice 9.9.0 (release line 9.9), built 2026-10-04. It is also published as markdown, with every table and list, at orset.md, and llms.txt lists every page.

tree.OrSet(key) -> OrSetAccessor, merge mode LatticeMergeMode.OrSet.

Semantics

An OR-Set is a distributed set of elements. You can add and remove elements from any replica concurrently, and the set still converges. Its defining rule is add-wins: if one replica removes an element while another concurrently adds it (or re-adds it), the element stays in the set.

It achieves this by tagging every add with a unique causal dot (replicaId, counter). A remove does not delete the element by name; it tombstones the specific dots it has observed. An add the remover never saw carries a fresh dot that no tombstone cancels, so it survives.

Elements are opaque byte[] (encode strings with Encoding.UTF8).

Use it for: shopping carts, tag sets, group membership, "online users" - anywhere a concurrent add should not be lost to a concurrent remove.

Behaviour

A concurrent OR-Set add and remove: add wins. An order diagram drawn as a chain of two states. The set starts empty. Cluster A adds apple, minting dot A1, which moves A up. Cluster B, at the same time, removes apple, but it has observed no dots for it, so there is nothing to tombstone and B's state stays at the bottom. Merging lifts B to A's state: apple is in the set, because B's remove never saw A1.

Both clusters hold {apple}. A remove cancels only the dots it has observed, and B's had observed none: add-wins.

A remove tombstones only the dots it has observed, so an add it never saw survives the merge: add-wins.

sequenceDiagram
    participant A as Cluster A
    participant B as Cluster B
    Note over A,B: key "cart:42" starts empty
    A->>A: add "apple"  (dot A1)
    B->>B: remove "apple" (sees no dots)
    A-->>B: merge (ships dot A1)
    B-->>A: merge (ships tombstones)
    Note over A,B: A1 was never observed by B's remove
    Note over A,B: converged set = { apple }  (add-wins)

Example

// Two clusters share one OR-Set under the same key.
var cart = tree.OrSet("cart:42");

// Cluster A adds "apple"; cluster B concurrently adds "pear".
await cart.AddAsync(Encoding.UTF8.GetBytes("apple"), "cluster-A", cancellationToken);
await cart.AddAsync(Encoding.UTF8.GetBytes("pear"), "cluster-B", cancellationToken);

// Once the states merge, both survive - adds accumulate.
bool hasApple = await cart.ContainsAsync(Encoding.UTF8.GetBytes("apple"), cancellationToken); // true
bool hasPear = await cart.ContainsAsync(Encoding.UTF8.GetBytes("pear"), cancellationToken);   // true

// A remove only tombstones the dots it has already observed. A concurrent
// re-add on another replica carries a fresh dot and wins the merge.
await cart.RemoveAsync(Encoding.UTF8.GetBytes("apple"), cancellationToken);

// Read the whole set back.
var set = await cart.GetAsync(cancellationToken);

See also: OR-Map (an OR-Set of keys whose values are themselves CRDTs) and the CRDT overview.