Maps (LWWMap, ORMap, GCounterMap)
Drei Map-CRDTs, ausgewählt danach, was die Werte sind:
| Typ | Wert-Typ | Wann |
|---|---|---|
LWWMap<K, V> | Plain Values (LWW pro Key) | Konfig pro Key — Flags, Einstellungen. |
ORMap<K, C> | Andere CRDTs | Counter, Sets, Register pro Key. |
GCounterMap<K> | Implizite GCounter-Werte | Counts pro Key (Clicks pro Page, Requests pro Route). |
GCounterMap<K> ist Zucker über ORMap<K, GCounter> — der
Fall ist so häufig, dass er einen eigenen Typ bekommt.
import { LWWMap } from 'actor-ts';
let m = LWWMap.empty<string, string>();m = m.put('node-a', 'theme', 'dark', 1000);m = m.put('node-b', 'lang', 'en', 2000);m.get('theme'); // → 'dark'm.get('lang'); // → 'en'LWW-Semantik pro Key — wie LWWRegister, aber indiziert. Nebenläufige Writes auf denselben Key werden über Timestamps aufgelöst; Writes auf verschiedene Keys kollidieren nicht.
m = m.remove('theme'); // ebenfalls LWW — jüngste Aktion gewinntremove wird ebenfalls als LWW-Operation getrackt — ein
kürzlicher Remove schlägt ein älteres Put, auch von einer
anderen Replika.
Nimm es für Konfig pro Key mit Einzelwert: User-Preferences, Feature-Flag-Overrides pro Tenant, Version pro Ressource.
import { ORMap, GCounter } from 'actor-ts';
let m = ORMap.empty<string, GCounter>();m = m.update('node-a', 'clicks', GCounter.empty, (c) => c.increment('node-a', 1),);m = m.update('node-b', 'views', GCounter.empty, (c) => c.increment('node-b', 1),);m.get('clicks')?.value(); // → 1Die Werte sind selbst CRDTs. Der Wert jedes Keys merged mit
dem eigenen Merge des Value-CRDT — GCounter-Keys mergen
per-Replika-Max, ORSet-Keys per Add-wins usw.
m = m.update('a', 'tags', () => ORSet.empty<string>(), (s) => s.add('a', 'urgent'),);Heterogene Wert-Typen sind erlaubt, aber abgeraten — eine einzelne ORMap hält typischerweise eine CRDT-Art pro Map. Wenn verschiedene Keys verschiedene Arten halten, kann das Framework die Typen nicht inferieren und du kämpfst mit dem Compiler.
Nimm es für replizierten State pro Key, der pro Key CRDT-Semantik braucht — Shopping-Carts pro User, Counter + Last-Changed-Register pro Feature.
m = m.remove('clicks');Einen Key aus ORMap zu entfernen nutzt die OR-Semantik —
getaggte Operation, die nebenläufige Re-Adds überschreiben
können. Gleiche “Add wins”-Semantik wie bei ORSet.
GCounterMap
Abschnitt betitelt „GCounterMap“import { GCounterMap } from 'actor-ts';
let m = GCounterMap.empty<string>();m = m.increment('node-a', 'page-home', 1);m = m.increment('node-a', 'page-about', 1);m = m.increment('node-b', 'page-home', 1);
m.get('page-home'); // → 2 (Summe über Replikas)m.get('page-about'); // → 1Eine GCounterMap<K> ist funktional ORMap<K, GCounter> —
eine Map, deren Werte jeweils ein Counter sind. Sie existiert,
weil Counts pro Key häufig genug sind (Page Views pro URL,
Clicks pro Button, Requests pro Route), um eine getypte
Abkürzung zu rechtfertigen.
Die API ist flacher:
m.increment(replicaId, key, delta); // den Counter eines Keys hochzählenm.get(key); // → number (Summe über Replikas)m.entries(); // → IterableIterator<[K, number]>Nimm sie überall, wo du in normalem Code Map<K, number>
schreiben würdest und Konvergenz bei nebenläufigen Writes
brauchst.
Auswahl zwischen ihnen
Abschnitt betitelt „Auswahl zwischen ihnen“| Wert-Typ pro Key | Map-Typ |
|---|---|
| Ein Plain Value mit Last-Writer-Wins-Auflösung | LWWMap |
| Ein CRDT (Counter, Set, Register, andere Map) | ORMap |
| Ein Counter (sehr häufiger Fall) | GCounterMap — Zucker über ORMap<K, GCounter> |
Häufige Formen:
- User-Preferences pro User —
LWWMap<UserId, UserPrefs>(Pref-Edits sind LWW; Konflikte sind selten). - Feature Flags pro Tenant —
LWWMap<TenantId, FlagSet>. - View Counts pro Ressource —
GCounterMap<ResourceId>. - Online Members pro Raum —
ORMap<RoomId, ORSet<UserId>>. - Rebalance Counts pro Shard —
GCounterMap<ShardId>.
Komposition
Abschnitt betitelt „Komposition“// Subscription-Set pro Raum + Nachrichtenzähler pro Raumlet rooms = ORMap.empty<string, ORSet<string>>();let counts = GCounterMap.empty<string>();
rooms = rooms.update('a', 'general', () => ORSet.empty<string>(), (s) => s.add('a', 'user-1'),);counts = counts.increment('a', 'general', 1);ORMap von ORSet ist die häufige Form für Map von Sets.
ORMap von LWWRegister<T> ist eine getypte Version von
LWWMap<K, T> mit denselben Semantiken (nimm direkt LWWMap,
wenn du genau das meinst).
Wohin als Nächstes
Abschnitt betitelt „Wohin als Nächstes“- Counter — für nicht-keyed Counter.
- Register — für nicht-keyed Einzelwerte.
- Sets — für nicht-keyed Collections.
- Daten gestalten — Auswahl zwischen flachen und Map-CRDTs.
Die API-Referenzen LWWMap,
ORMap und
GCounterMap decken die
vollständige Oberfläche ab.