Replikationsfaktor ist keine Ausfallsicherheit: Einen Zaris-Cluster auf Kubernetes für Dauerhaftigkeit dimensionieren
Beim Deployment eines replizierten Stores auf Kubernetes liegt ein verlockender Gedankengang nahe: Ich habe replicationFactor: 2 gesetzt, also hat jede Partition ein Backup, also überstehe ich den Verlust eines Nodes. Das fühlt sich wasserdicht an. Für eine überraschend häufige Clustergröße ist es dennoch falsch — und zwar auf die stille Art, die erst auffällt, wenn tatsächlich zum ersten Mal ein Pod stirbt.
Zaris auf Kubernetes speichert Daten im Arbeitsspeicher und macht sie durch Replikation, nicht durch Festplatte dauerhaft: Das Node-Datenvolume ist ein emptyDir, und ein neu eingeplanter Pod baut sich aus der Kopie auf einem anderen Pod wieder auf. Dieses Design ist solide — verlässt sich aber vollständig darauf, dass es tatsächlich eine Kopie auf einem anderen Pod gibt. Ob diese Kopie existiert, entscheidet nicht replicationFactor allein. Es entscheidet die Arithmetik zwischen Pod-Anzahl und Replikationsfaktor, und wenn Sie diese Arithmetik falsch ansetzen, läuft der Store einwandfrei, bedient jeden Lese- und Schreibzugriff und hält stillschweigend eine Partition, die nirgendwohin replizieren kann.
Die Deployment-Form
Das Zaris-Helm-Chart deployt die Node-Ebene als StatefulSet, eine Node-Instanz pro Pod (zaris-0, zaris-1, …), jede ein In-Memory-Shard-Owner. Ein Headless-Service gibt jedem Pod eine stabile DNS-Identität, die Readiness ist an die Cluster-Konvergenz gekoppelt, und ein PodDisruptionBudget plus ein Termination-Fenster mit sanftem Drain schützen die Menge bei freiwilligen Unterbrechungen.
# values.yaml (defaults)
node:
replicas: 4 # pod count — ONE node instance per pod
replicationFactor: 2 # copies of each partition, on distinct pods
Die eigenen Hinweise des Charts sind unmissverständlich, woher die Dauerhaftigkeit kommt:
Node data is emptyDir. In-memory durability is via replication (RF), not disk; a rescheduled node rebuilds from replicas.
Ein Node-Pod kann also den ganzen Tag beendet und neu eingeplant werden, ohne Datenverlust — vorausgesetzt, seine Partitionen haben ein lebendes Replikat auf einem anderen Pod, aus dem er sich wieder aufbauen kann. Diese Voraussetzung ist das ganze Spiel.
Wie Partitionen auf Pods landen
Eine Partition in Zaris wird von RF verschiedenen Node-Instanzen gehalten — ein aktiver Owner plus RF−1 Replikate —, und die Replikate werden stets auf verschiedenen Pods platziert (ein Replikat auf demselben Pod wie sein Aktiver wäre überhaupt kein Schutz). Die Runtime platziert Partitionen gierig:
The runtime fills a partition to its full RF before forming the next one.
Dieser eine Satz ist der Schlüssel zur gesamten Dimensionierung. Instanzen werden RF auf einmal verbraucht. Die Anzahl vollständig replizierter Partitionen ist daher:
fully-replicated partitions = ⌊ instances ÷ RF ⌋
und alle übrig gebliebenen Instanzen — instances mod RF davon — bilden eine Einzelkopie-Partition ohne Peer, auf den repliziert werden könnte. Sie ist eine echte, bedienende Partition. Sie hat nur kein Backup.
Mit vier Pods bei RF 2 erhalten Sie zwei Partitionen mit je zwei Kopien auf verschiedenen Pods — verlieren Sie einen beliebigen Pod, läuft die überlebende Kopie weiter, während die Partition sich auf den neu eingeplanten Pod re-repliziert. Mit drei Pods bei RF 2 bilden Pod 0 und 1 die Partition A mit vollem RF, Pod 2 bleibt aber übrig und bildet die Partition B mit nur einer Kopie. Fügen Sie einen vierten Pod hinzu, wird er zum Replikat von B. Bis dahin ist B eine Belastung.
Die Dimensionierungsregel als Tabelle
Da jede Partition RF verschiedene Pods braucht und Kubernetes eine Instanz pro Pod betreibt, lautet die Regel schlicht:
Betreiben Sie eine Pod-Anzahl, die ein Vielfaches von RF ist. Bei RF 2 verwenden Sie eine gerade Anzahl Pods.
| Pods (je 1 Instanz) | RF | Vollständig replizierte Partitionen | Übersteht den Verlust eines Pods? |
|---|---|---|---|
| 2 | 2 | 1 | Ja |
| 3 | 2 | 1 (+ 1 Einzelkopie) | Nein — der Verlust des Einzelkopie-Pods verliert diese Partition |
| 4 | 2 | 2 | Ja |
| 6 | 2 | 3 | Ja |
Das Muster lässt sich verallgemeinern: N Partitionen bei RF 2 brauchen 2N Pods; bei RF 3 brauchen sie 3N. Hochskalieren heißt, Pods jeweils um einen vollen RF hinzuzufügen, wenn jede neue Partition von Geburt an dauerhaft sein soll.
Warum drei Pods mit RF 2 die Falle sind
Drei ist die Zahl, die zubeißt, denn drei Pods sehen aus wie ein gesunder, hochverfügbarer Cluster. Er besteht die Readiness. Er bedient Datenverkehr. kubectl get pods zeigt zaris-0..2 alle als 1/1 Ready. Nichts ist offensichtlich falsch.
Was falsch ist, bleibt unsichtbar, bis der Einzelkopie-Pod — der, der die einzige Kopie der Partition B hält — der Pod ist, der stirbt. In diesem Moment sind die Keys von B endgültig weg, weil es nie eine zweite Kopie gab, aus der man sich hätte aufbauen können. Das ist kein Bug; es ist dieselbe Einschränkung wie beim Betrieb auf einer einzelnen Maschine, nur auf eine Partition statt auf den gesamten Store begrenzt. Die Map wird nur neu erzeugt, wenn alle Kopien einer Partition verloren sind, und bei einer Einzelkopie-Partition ist „alle Kopien" ein Pod.
Das wurde gemessen, nicht angenommen. Auf einem kind-Cluster:
- Bei
replicas=4(2 Partitionen × RF 2) verlor das Beenden des Active-Owner-Pods einer Partition 0 von 200 Keys — das Replikat übernahm, und RF stellte sich auf dem neu eingeplanten Pod selbst wieder her. - Bei
replicas=3verlor das Beenden des Einzelkopie-Pods die Keys dieser Partition vollständig.
Derselbe Store, dieselbe RF-Einstellung, dasselbe Chaos. Die einzige Variable war, ob die Pod-Anzahl ein Vielfaches von RF war.
Die Leitplanken, die das Chart Ihnen bietet
Das Chart überlässt das nicht der Folklore. Installieren Sie einen Cluster, dessen Pod-Anzahl kein Vielfaches von RF ist, gibt helm install in seinen Hinweisen eine Dauerhaftigkeitswarnung aus:
⚠ DURABILITY WARNING
node.replicas (3) is not a multiple of node.replicationFactor (2), so at least one
partition is SINGLE-COPY and will LOSE its data if that pod is lost. For fault
tolerance set node.replicas to a multiple of 2 (e.g. 4).
Über die Warnung hinaus sind mehrere Defaults so gesetzt, dass ein korrekt dimensionierter Cluster sich bei Unterbrechungen gut verhält:
- Durch Konvergenz gesteuerte Readiness.
/readyzbesteht erst, wenn Partition-Map und Zuständigkeit live sind, sodass einReady-Pod ein routbarer ist — Kubernetes sendet keinen Datenverkehr an einen Node, der die Zuständigkeit noch nicht übernommen hat. - PodDisruptionBudget. Standardmäßig wird höchstens ein Node gleichzeitig freiwillig unterbrochen, sodass ein Drain oder Rolling Upgrade nicht beide Kopien einer Partition auf einmal ausschalten kann.
- Sanfter Drain bei Termination. Ein Termination-Grace-Fenster lässt einen ausscheidenden Node sauber übergeben, statt mitten im Betrieb abgerissen zu werden.
- Hot Scale-out. Bei aktivierter Kubernetes-Discovery (Default) wird der Roster aus Pod-Ordinalzahlen und Headless-DNS synthetisiert, sodass das Hinzufügen von Pods ein schlichtes
kubectl scale statefulsetist — neue Pods treten bei, und die Partition-Map wächst, ohne die bestehenden neu zu starten. Fügen Sie sie jeweils in einem Vielfachen von RF hinzu, und jede neue Partition ist von Geburt an dauerhaft.
Keine davon hilft jedoch der Einzelkopie-Partition. Ein PDB, das alle bis auf einen Pod oben hält, schützt nicht, wenn einer dieser Pods die einzige Heimat einer Partition ist. Die Leitplanken setzen voraus, dass Sie den Cluster korrekt dimensioniert haben; einen falsch dimensionierten Cluster retten sie nicht.
Praktische Regeln
Das ganze Thema lässt sich auf ein paar Gewohnheiten verdichten:
- Wählen Sie zuerst RF, dann machen Sie die Pod-Anzahl zu einem Vielfachen davon. RF 2 → gerade Pod-Anzahlen; RF 3 → Vielfache von drei. Das Standard-Chart liefert 4 Pods bei RF 2, gerade weil dies die kleinste ausfallsichere Form ist.
- Behandeln Sie eine ungerade Pod-Anzahl als reine Evaluierung. Drei Pods bei RF 2 sind für eine Demo auf
kindin Ordnung; als Dauerhaftigkeitshaltung in der Produktion taugen sie nicht, und das Chart sagt Ihnen das bei der Installation. - Skalieren Sie in RF-Schritten. Einen Pod zu einem dauerhaften Cluster hinzuzufügen, erzeugt vorübergehend eine Einzelkopie-Partition, bis der nächste Pod eintrifft. Wenn Sie für Dauerhaftigkeit wachsen, wachsen Sie um ein ganzes Replikations-Set auf einmal.
- Denken Sie daran, dass das Volume ein emptyDir ist. Es gibt keine Festplatte als Rückfallebene. Replikation ist Ihre Dauerhaftigkeit, und genau deshalb muss die Kopie tatsächlich auf einem anderen Pod existieren.
Der Replikationsfaktor sagt, wie viele Kopien eine Partition haben will. Die Pod-Anzahl entscheidet, ob sie sie haben kann. Bringen Sie diese beiden Zahlen in Einklang, und ein Pod-Verlust ist ein Nicht-Ereignis — das Replikat bedient, der neu eingeplante Pod baut sich wieder auf, und kein Key geht verloren. Lassen Sie sie auseinanderdriften, haben Sie einen Cluster gebaut, der hochverfügbar ist — bis genau zu dem Moment, auf den es ankommt.