Zum Inhalt springen
Deutsch

Maps (LWWMap, ORMap, GCounterMap)

Drei Map-CRDTs, ausgewählt danach, was die Werte sind:

TypWert-TypWann
LWWMap<K, V>Plain Values (LWW pro Key)Konfig pro Key — Flags, Einstellungen.
ORMap<K, C>Andere CRDTsCounter, Sets, Register pro Key.
GCounterMap<K>Implizite GCounter-WerteCounts 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 gewinnt

remove 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(); // → 1

Die Werte sind selbst CRDTs. Der Wert jedes Keys merged mit dem eigenen Merge des Value-CRDTGCounter-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.

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'); // → 1

Eine 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ählen
m.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.

Wert-Typ pro KeyMap-Typ
Ein Plain Value mit Last-Writer-Wins-AuflösungLWWMap
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 UserLWWMap<UserId, UserPrefs> (Pref-Edits sind LWW; Konflikte sind selten).
  • Feature Flags pro TenantLWWMap<TenantId, FlagSet>.
  • View Counts pro RessourceGCounterMap<ResourceId>.
  • Online Members pro RaumORMap<RoomId, ORSet<UserId>>.
  • Rebalance Counts pro ShardGCounterMap<ShardId>.
// Subscription-Set pro Raum + Nachrichtenzähler pro Raum
let 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).

  • 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.