Sets (GSet, ORSet)
Zwei Set-CRDTs:
| Typ | Operationen | Wann |
|---|---|---|
GSet<E> | nur add | Append-only-Collections — beobachtete Arten, gesehene User. |
ORSet<E> | add + remove | Membership-Sets, deren Elemente kommen und gehen. |
Beide konvergieren bei nebenläufigen Writes; der Spezial-Trick
von ORSet ist die Auflösung von “nebenläufigem Add und Remove
desselben Elements”, damit Removes nicht versehentlich ein noch
gewolltes Item löschen.
import { GSet } from 'actor-ts';
let s = GSet.empty<string>();s = s.add('apple');s = s.add('banana');s.value(); // → Set(['apple', 'banana'])Der State ist ein einfaches Set; Merge ist eine Vereinigung. Kann nicht entfernen — einmal hinzugefügt, bleibt ein Element für immer drin.
Nimm es für:
- Beobachtete Event-Typen, die das System je gesehen hat.
- User, die sich je eingeloggt haben (ein
unique-users-ever-Set). - Tags, die auf eine Ressource angewendet und nicht wieder entfernt werden.
Wenn du Removes brauchst, ist GSet die falsche Form; greif zu
ORSet.
Element-Identität
Abschnitt betitelt „Element-Identität“let s = GSet.empty<UserSession>({ identity: (u) => u.id });s = s.add({ id: 'user-42', name: 'Alice' });s = s.add({ id: 'user-42', name: 'Alice updated' });s.value().size; // → 1 (gleiche Identität)Standardmäßig werden Elemente per JSON.stringify(element)
dedupliziert. Übergib eine identity: (e) => string-Funktion
für eigene Dedup-Logik — nützlich, wenn Elemente Objekte mit
stabilen IDs, aber veränderlichen anderen Feldern sind.
ORSet (Observed-Remove Set)
Abschnitt betitelt „ORSet (Observed-Remove Set)“import { ORSet } from 'actor-ts';
let s = ORSet.empty<string>();s = s.add('node-a', 'apple');s = s.add('node-b', 'banana');s = s.remove('apple');s.value(); // → Set(['banana'])Der Trick: jedes add stempelt das Element mit einem
eindeutigen Tag; remove(e) entfernt nur die Tags, die für
die entfernende Replika gerade sichtbar sind.
Damit löst sich der Fall “nebenläufig Add + Remove” zugunsten des Add auf:
let a0 = ORSet.empty<string>().add('a', 'apple');let b0 = a0; // beide sehen 'apple' mit demselben Tag
let a1 = a0.remove('apple'); // A entferntlet b1 = b0.add('b', 'apple'); // B fügt ein NEUES 'apple' hinzu (neuer Tag)
a1.merge(b1).value(); // → Set(['apple']) — Bs Add überlebtDas ursprüngliche ‘apple’ hatte den Tag, den B nie sah, also führt Bs frisches Add einen neuen Tag ein. Beim Merge von A und B entfernte As Remove nur den ursprünglichen Tag; Bs Tag bleibt. Nettoergebnis: ‘apple’ ist im Set.
Diese “Add wins”-Semantik macht ORSet zur richtigen Wahl für Shopping-Carts, Presence-Sets, Subscription-Listen — Sets, in denen ein kurzzeitig von einer Partei entferntes Item nicht verschwinden sollte, wenn eine andere Partei es nebenläufig wieder hinzufügt.
Tag-Generierung
Abschnitt betitelt „Tag-Generierung“Tags sind replicaId × per-replica-counter. Jede Replika hält
einen monotonen Counter; jedes add erhöht ihn. Das garantiert
eindeutige Tags selbst innerhalb derselben Millisekunde, über
den ganzen Cluster.
Der Counter ist Teil des CRDT-State — Gossipen macht es korrekt. Du siehst und manipulierst Tags nicht direkt; sie sind intern.
Element-Identität (wie bei GSet)
Abschnitt betitelt „Element-Identität (wie bei GSet)“const s = ORSet.empty<CartItem>({ identity: (i) => i.sku });s.add('node-a', { sku: 'book-1', price: 10 });s.add('node-b', { sku: 'book-1', price: 12 });// → trotzdem ein Eintrag (gleiche SKU), aber zwei Tags// — die Tags sind unabhängig von der Identität des WertsDie Identity-Funktion bestimmt die Deduplikation; die Tag-Mechanik arbeitet pro Add unabhängig davon.
Auswahl zwischen beiden
Abschnitt betitelt „Auswahl zwischen beiden“| Frage | Typ |
|---|---|
| Brauchst du jemals Removes? | ORSet |
| Sonst | GSet |
GSet ist kleiner (keine Tag-Buchhaltung) und einfacher.
Nimm es, wenn “Item entfernen” nicht Teil des Workflows ist.
Die zusätzlichen Kosten von ORSet sind das Tag-Tracking — für
Sets mit hoher Churn (häufige Add/Remove-Zyklen auf demselben
Element) wächst der interne State. Siehe “Wann ORSet nicht
passt” unten.
Wann ORSet nicht passt
Abschnitt betitelt „Wann ORSet nicht passt“Wohin als Nächstes
Abschnitt betitelt „Wohin als Nächstes“- Counter — für additive Counts.
- Register — für Einzelwert-Writes.
- Maps — für keyed Assoziationen inklusive ORMap.
- Daten gestalten — der konzeptionelle Leitfaden.
Die API-Referenzen GSet und
ORSet decken die vollständige
Oberfläche ab.