Zum Inhalt springen
Deutsch

Downing-Strategien

Wenn der Cluster partitioniert wird, laufen beide Hälften weiter — und beide denken, die andere Hälfte sei ausgefallen. Ohne Eingriff:

  • Es würden zwei Singletons existieren (einer pro Seite).
  • Sharded Entities für denselben Key könnten auf beiden Seiten gespawnt werden.
  • DistributedData-Replicas würden bis zur Versöhnung divergieren.

Eine Downing-Strategie wählt eine Gewinnerseite und fährt die Verliererseite zwangsweise herunter. Die Actors der verlierenden Nodes stoppen; die Gewinnerseite läuft als Cluster weiter.

Netzwerk-Split

Während der Partition (KeepMajority entscheidet)

n1 · n2 · n3

Mehrheit → läuft weiter

n4 · n5

Minderheit → fährt sich selbst herunter

Vor der Partition

n1 · n2 · n3 · n4 · n5

Ohne Strategie: beide Hälften laufen weiter. Wenn die Partition heilt, hast du zwei getrennte Cluster mit demselben Namen und keinen automatischen Merge. Konfiguriere immer eine Strategie für Produktion.

StrategieGewinnt bei…Trade-off
KeepMajorityDer Seite mit > N/2 Mitgliedern.Einfach; Unentschieden (genau N/2-N/2-Split) bedeutet, beide Seiten gehen down.
KeepOldestDer Seite mit dem ältesten Mitglied.Funktioniert für gerade Cluster-Größen, in denen Mehrheit undefiniert ist.
KeepRefereeDer Seite mit einem bestimmten Schiedsrichter-Node.Vorhersagbar, schafft aber einen Single Point of Failure (den Schiedsrichter).
StaticQuorumDer Seite, die eine konfigurierte Quorum-Größe erreicht.Strenger als Mehrheit; die Quorum-Größe ist die Wahl des Operators.
LeaseMajorityMehrheit + muss eine Koordinations-Lease halten.Paranoide Sicherheit; erfordert einen Lease-Provider (K8s etc.).

Wähle nach der Topologie deines Clusters und den operativen Randbedingungen (siehe Auswahlabschnitt unten).

import { Cluster, KeepMajority } from 'actor-ts';
const cluster = await Cluster.join(system, {
host, port, seeds,
downingProvider: new KeepMajority(),
});

Übergib den Provider als downingProvider. Jedes Cluster-Event (Mitglied unreachable, Mitglied reachable etc.) führt den Provider mit der aktuellen Sicht erneut aus; er gibt die Menge der Adressen zurück, die zwangsweise herabgestuft werden sollen. Eine leere Menge bedeutet “noch keine Entscheidung — abwarten.”

import { KeepMajority } from 'actor-ts';
new KeepMajority();

Die klassische Strategie. Zählt Mitglieder; die Seite mit mehr gewinnt.

Macht gut:

  • Allgemeinster Default.
  • Keine externen Abhängigkeiten.
  • Vorhersagbar bei ungeradem N.

Macht nicht:

  • Exakte N/2-N/2-Splits handhaben — beide Seiten fahren sich selbst herunter.
  • Zwischen “5 Mitglieder gesund” und “5 Mitglieder alle auf derselben Maschine, das Rack brennt” unterscheiden — Mehrheit per Zählung ist blind für physische Topologie.

Richtig für Cluster mit einer ungeraden Zahl an Nodes und ohne besondere Deployments.

import { KeepOldest } from 'actor-ts';
new KeepOldest({ downIfAlone: true });

Die Seite mit dem ältesten Mitglied (am längsten im Cluster) gewinnt. Nützlich, wenn:

  • Du eine gerade Zahl an Nodes hast, bei denen KeepMajority bei 50/50-Splits mehrdeutig wird.
  • Der Cluster einen langlebigen “stabilen” Node hat (einen Koordinator-Pod), der fast immer der älteste ist und die Strategie deterministisch macht.

downIfAlone: true bedeutet “wenn ich der älteste bin, aber alle anderen unreachable sind, fahre ich mich selbst herunter” — verhindert, dass ein Ein-Node-”Gewinner” sich nach einem Komplett-Split selbst zum Cluster erklärt.

import { KeepReferee, NodeAddress } from 'actor-ts';
new KeepReferee({
refereeAddress: NodeAddress.parse('actor-ts://my-app@10.0.0.1:2552'),
downAllIfLessThanNodes: 3,
});

Ein bestimmter Schiedsrichter-Node ist das entscheidende Mitglied. Die Seite mit dem Schiedsrichter gewinnt; die andere Seite fährt sich herunter.

Macht gut:

  • Die vorhersagbarste Strategie — keine Mehrdeutigkeit darüber, welche Seite gewinnt.
  • Funktioniert für jede Cluster-Größe, auch gerade.

Macht nicht:

  • Den Schiedsrichter selbst überleben, wenn er ausfällt. Verschwindet der Schiedsrichter, hat ihn keine Seite, und die Strategie kann nicht entscheiden. Daher downAllIfLessThanNodes — “wenn der Cluster unter dieser Größe ist, fahre alles herunter und lass Operatoren neu aufbauen.”

Nützlich für Zwei-DC-Cluster mit einem Tie-Breaker-Node an einem neutralen Ort (drittes DC, eine Control-Plane-K8s-Namespace).

import { StaticQuorum } from 'actor-ts';
new StaticQuorum({ quorumSize: 3 });

Eine Seite gewinnt nur, wenn sie mindestens quorumSize erreichbare Mitglieder hat. Unterhalb des Quorums fährt die Seite sich selbst herunter.

Macht gut:

  • Strenger als Mehrheit — schützt vor Szenarien, in denen die Minderheit sonst weiterlaufen würde.
  • Konfigurierbar nach der Konfidenzschwelle deines Operators.

Macht nicht:

  • Sich automatisch erholen. Bilden sich mehrere Sub-Quorum-Partitionen, gewinnt keine; der Operator muss manuell neu aufbauen.

Richtig, wenn du eher Fail-Stop willst, als ein Falsche-Seite-Überleben zu riskieren.

import { LeaseMajority } from 'actor-ts';
new LeaseMajority({
underlying: new KeepMajority(),
lease: someLeaseImpl,
});

Mehrheit plus eine Koordinations-Lease. Wrappe jede andere Strategie: die Gewinnerseite muss zusätzlich eine Lease (z. B. eine K8s-Lease-Ressource) erwerben, bevor sie sich als autoritativ betrachtet.

Macht gut:

  • Doppelt gemoppelte Sicherheit. Zweifachprüfung.
  • Nützlich, wenn das Netzwerk unberechenbar ist (z. B. Cloud-Cross-Zone).

Macht nicht:

  • Helfen, wenn der Lease-Provider selbst weg-partitioniert ist.

Richtig für paranoide Produktionsszenarien, in denen du eher doppelten Aufwand für Split-Brain-Schutz akzeptierst.

Drei Fragen, in dieser Reihenfolge:

  1. Gerade oder ungerade Cluster-Größe?

    • Ungerade → KeepMajority.
    • Gerade → KeepOldest oder KeepReferee (Unentschieden vermeiden).
  2. Hast du einen stabilen Tie-Breaker-Node?

    • Ja → KeepReferee (am vorhersagbarsten).
    • Nein → KeepMajority oder KeepOldest.
  3. Ist Fail-Stop besser als ein potenzieller Falsche-Seite-Gewinn?

    • Ja → StaticQuorum (irrt auf der Stop-Seite).
    • Nein → eine der oben genannten.

Für ein typisches 3-Node-K8s-Deployment: KeepMajority. Für ein 2-DC-Setup mit einem Tie-Breaker in der dritten Region: KeepReferee. Für einen 5-Node-Cluster, der niemals unter 3 fallen soll: StaticQuorum(3).

Das Provider-Interface ist winzig:

interface DowningProvider {
decide(view: ClusterPartitionView): DowningDecision;
}
interface ClusterPartitionView {
allMembers: ReadonlyArray<Member>;
unreachable: ReadonlySet<string>; // Adress-Strings
self: NodeAddress;
}

Implementiere decide(view) => Set<string> und gib die Adress-Strings zurück, die herabgestuft werden sollen. Leere Menge heißt “keine Entscheidung.”

Nützlich für app-spezifische Regeln — z. B. “behalte immer den Node mit role=primary” oder “wenn die Partition den DB-Master-Node enthält, gewinnt diese Seite.”

Die DowningProvider API-Referenz deckt das Strategie-Interface ab.