RW-Set (Remove-Wins 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 rwset.md, and llms.txt lists every page.tree.RwSet(key) -> RwSetAccessor, merge mode LatticeMergeMode.RwSet.
Semantics
An RW-Set is a set whose membership converges remove-wins: when a concurrent add and remove of the same element race, the remove wins and the element stays out. It is the set-granularity generalisation of the RW-Flag - an RW-Flag is a single-element RW-Set, exactly as an OR-Flag is to an OR-Set.
Each element carries three dot sets: add dots, remove dots, and observed-add
tombstones that cancel removes. An element is a member only when it carries an
add dot and no remove dot survives. A Remove that an add has not observed
survives the merge and keeps the element out, so a revoke is never silently
resurrected by a concurrent re-add.
Both Add and Remove take a replicaId (each side stamps its own dots). Use
it for membership revocation lists and blocklists - any set where a removal must
win the tie. When a concurrent add should instead win, reach for the add-wins
OR-Set.
Behaviour
Both clusters hold add A1 and remove B1, so user:88 is not a member. A surviving remove keeps the element out: remove-wins.
An element is a member only while it has an add dot and no remove dot survives, so the remove wins the race.
sequenceDiagram
participant A as Cluster A
participant B as Cluster B
A->>A: Add("user:88", dot A1)
B->>B: Remove("user:88", dot B1, observed: none)
A-->>B: merge ships add A1
B-->>A: merge ships remove B1
Note over A,B: B1 was not observed by the add
Note over A,B: converged = NOT a member (remove-wins)
Example
var members = tree.RwSet("tenant:7:members");
// Cluster A re-admits a user; cluster B concurrently revokes them.
await members.AddAsync(Encoding.UTF8.GetBytes("user:88"), "cluster-A", cancellationToken);
await members.RemoveAsync(Encoding.UTF8.GetBytes("user:88"), "cluster-B", cancellationToken);
// The revoke was not observed by the add, so it survives and the element
// converges OUT of the set - a revocation is never lost to a concurrent add.
bool stillMember = await members.ContainsAsync(Encoding.UTF8.GetBytes("user:88"), cancellationToken); // false
See also: its add-wins mirror OR-Set, the single-element RW-Flag, and the CRDT overview.