Zum Inhalt springen
Deutsch

Quorum Reads und Writes

Die Default-Operationen update / get von DistributedData sind nur lokal — sie werden sofort auf die lokale Replika angewendet; die Propagation passiert via Gossip. Für Workloads, die stärkere Garantien brauchen, warten die Varianten updateAsync und getAsync auf explizite Replika-Bestätigungen.

dd.update<GCounter>(
'hits',
GCounter.empty,
(c) => c.increment(dd.selfReplicaId(), 1),
);
const counter = dd.get<GCounter>('hits');

Diese Operationen:

  • update — wende lokal an, fire-and-forget. Kehrt sofort zurück.
  • get — lese die Sicht der lokalen Replika.

Unter der Haube propagiert Gossip Updates über die nächsten Runden an andere Replikas.

Das ist der richtige Default — 99 % der DD-Operationen nutzen diese.

Drei häufige Fälle:

  1. Read-after-Write über Nodes hinweg. Node A schreibt, Node B liest sofort — der Write ist vielleicht noch nicht gegossipt, also sieht Node B veraltete Daten.
  2. Bestätigter Write. Die App will wissen “mindestens N andere Replikas haben mein Update, bevor ich weitermache” — z. B. bevor dem Client geantwortet wird.
  3. Strongest-known Read. Wenn Veraltung für diesen Read inakzeptabel ist (Saldo-Check vor einer Zahlung), zwinge den Read, die Mehrheit der Replikas zu konsultieren.
await dd.updateAsync<GCounter>(
'hits',
GCounter.empty,
(c) => c.increment(dd.selfReplicaId(), 1),
{ consistency: 'majority', timeoutMs: 2_000 },
);

Wendet lokal an + schickt an die Gossip-Schicht und wartet dann auf Bestätigungen der konfigurierten Anzahl Replikas.

consistencyWorauf gewartet wird
'local' (Default)Nur auf sich selbst — das lokale Anwenden ist das ACK.
'majority'⌈N/2 + 1⌉ Replikas haben geACKt.
'all'Jede Replika der Up-Member hat geACKt.
{ kind: 'count'; n: 3 }Genau n Replikas haben geACKt.

Das Promise:

  • Resolved, wenn genug ACKs innerhalb von timeoutMs eintreffen.
  • Rejected mit einem Timeout-Fehler, wenn nicht.

Ein Timeout macht den lokalen Write nicht rückgängig — der Wert ist bereits lokal angewendet und gossipt normal weiter. Der Reject signalisiert nur “ich bin mir nicht sicher, ob genug Replikas ihn innerhalb der Deadline gesehen haben”.

const counter = await dd.getAsync<GCounter>('hits',
{ consistency: 'majority' });

Dieselben consistency-Optionen. Der Replicator fragt andere Replikas nach ihrer Sicht, merged die Antworten und gibt den gemergten Wert zurück.

Das heißt, ein Majority Read sieht mindestens jeden Write, der Majority-bestätigt wurde — den letzten “confirmed”-Wert.

ja — die meisten Fälle

nein — muss von genug

Replikas bestätigt sein

absolut nicht — jedes

Up-Member muss zustimmen

Sind veraltete Daten für DIESEN

Read/Write akzeptabel?

'local'

'majority'

'all'

'majority' ist der Sweet Spot für “wichtige” Reads/Writes — deckt die meisten Failure-Szenarien zu moderater Latenz ab. 'all' garantiert Konsistenz, scheitert aber, wenn auch nur eine Replika down (oder langsam) ist — das macht es spröde.

// Mit Majority schreiben — garantiert, dass andere Replikas Bescheid wissen
await dd.updateAsync('counter', ..., { consistency: 'majority' });
// Lokal lesen — schnell, und der Wert spiegelt (eventually) wider,
// was andere Writer mit Majority geschrieben haben
const value = dd.get('counter');

Das ist das häufige Produktionsmuster. Writes zahlen die Majority-Kosten; Reads sind billig. Der Cluster konvergiert irgendwann.

// Majority lesen — den letzten bekannten Wert im Cluster sehen
const balance = await dd.getAsync<PNCounter>('balance',
{ consistency: 'majority' });
if (balance.value() < 0) {
// ... Transaktion ablehnen
}

Für einmalige Reads, bei denen Veraltung zählt, zahl die Kosten für einen starken Read. Mach nicht jeden Read zu einem Majority Read — das hebelt das Local-First-Design aus.

await dd.updateAsync('config-locked', ..., { consistency: 'all' });

Selten — wenn du brauchst, dass jede Replika die Änderung gesehen hat, bevor es weitergeht. Spröde (jede ausgefallene Replika lässt die Operation scheitern), also wirklich nur für echt-unwiderrufliche Zustandsübergänge.

await dd.updateAsync('x', ..., {
consistency: 'majority',
timeoutMs: 5_000,
});

Default-Timeout ist gossipIntervalMs × 5 — fünf Gossip-Runden, typischerweise 5 Sekunden. Pro Call überschreiben, wenn:

  • Strengeres Latenz-Budget — niedriger setzen; früh ablehnen, wenn Konsistenz nicht erreicht werden kann.
  • Lockerer — bei cross-region Clustern mit hoher Gossip-RTT das Timeout erhöhen.

Bei Timeout wirft das Promise einen Fehler, den du fangen kannst:

try {
await dd.updateAsync('x', ..., { consistency: 'majority', timeoutMs: 1_000 });
} catch (e) {
// Der Write hat lokal funktioniert, aber das Quorum kam nicht rechtzeitig.
// Entscheide: erneut versuchen, Fehler nach oben geben, Eventual-Consistency akzeptieren.
}