Zum Hauptinhalt springen

Zwei Cluster, ein Change Feed: Replikation über ein WAN, ohne Zaris zu strecken

· 10 Minuten Lesezeit
Clustron Team
Engineering für verteilte Systeme

Zwei unabhängige Cluster, verbunden durch einen wiederaufnehmbaren Change Feed über ein WAN

Früher oder später stellt jemand die Frage: Wir haben einen Cluster in einer Region und Nutzer in einer anderen — können wir nicht einfach dort ein paar Nodes hinzufügen und einen Cluster beide Regionen umspannen lassen? Das ist ein nachvollziehbarer Instinkt. Ein Cluster, ein Namespace, ein Connection-String, Daten überall. Der Instinkt ist aber auch eine Falle, und es lohnt sich, genau zu erklären, warum.

Ein Zaris-Cluster ist für ein Datacenter-Netzwerk konstruiert. Die Mitgliedschaft wird mit Heartbeats auf kurzen Timern verfolgt. Das Ownership einer Partition wechselt zwischen Nodes über einen Handshake, der voraussetzt, dass die Gegenseite schnell antwortet. Das Failover fenced einen vermutlich toten Owner und befördert eine Replica im Sekundenbereich. Jeder dieser Mechanismen ist für eine Verbindung kalibriert, bei der die Round-Trip-Zeit in Mikrosekunden gemessen wird und ein „langsamer" Node tatsächlich krank ist — nicht bloß achtzig Millisekunden und einen Ozean entfernt. Setzt man die Hälfte der Nodes über ein WAN, hat man keinen georedundanten Cluster gebaut, sondern einen Cluster, der normale WAN-Latenz nun für einen Ausfall hält, das Ownership flattern lässt und beim ersten Aussetzer der Verbindung zwischen den Regionen ein Split-Brain riskiert.

Die richtige Form ist also nicht ein gestreckter Cluster. Es sind zwei unabhängige Cluster — jeder in sich geschlossen, mit eigenem Ownership und Failover, das vollständig im eigenen LAN bleibt —, verbunden durch einen asynchronen, wiederaufnehmbaren Change Feed. Dieser Beitrag handelt von diesem Muster: warum Unabhängigkeit die tragende Entscheidung ist und wie das Operation-Log von Zaris Ihnen bereits das Eine liefert, was ein WAN-Replikator tatsächlich braucht — einen Cursor, von dem aus Sie fortsetzen können.

Warum ein gestreckter Cluster das falsche Werkzeug ist​

Es hilft, die konkreten Annahmen zu benennen, die ein einzelner Cluster trifft, denn jede zerbricht über ein WAN auf andere Weise.

  • Heartbeat-basierte Mitgliedschaft entscheidet, dass ein Node verschwunden ist, wenn er einige Beats verpasst. Eine Verbindung zwischen Regionen, die Latenz hinzufügt und gelegentlich partitioniert, wird diesen Detektor im gesunden Betrieb auslösen. Der Cluster wird lebende Nodes für tot erklären.
  • Das Ownership-Protokoll verschiebt Partitionen zwischen Nodes mit einem Handshake, der eine prompte Antwort erwartet. Über ein WAN stocken, wiederholen und konkurrieren diese Übergänge — genau der Churn, den die Ownership- und Cut-over-Mechanik in einem LAN schnell auflösen soll.
  • Failover-Timer befördern eine Replica, wenn der Owner tot aussieht. Über ein WAN sind „sieht tot aus" und „ist nur weit weg" nicht mehr unterscheidbar, sodass es zu unnötigen Promotions kommt — und wenn die Verbindung heilt und auf jeder Seite eine beförderte Replica sitzt, haben Sie zwei Owner für eine Partition.

Nichts davon ist ein Defekt. Es ist ein Store, der genau das tut, was ein Store mit niedriger Latenz im Datacenter tun sollte, angewendet auf ein Netzwerk, das er nie überspannen sollte. Die Lösung ist nicht, jeden Timer so weit zu lockern, bis der Cluster ein WAN toleriert — das würde ihn auch echte Ausfälle viel zu langsam tolerieren lassen. Die Lösung ist, nicht länger von einem Cluster zu verlangen, es zu tun.

Der Baustein, den Sie bereits haben: ein wiederholbares Log​

Zaris führt pro Segment ein nach Revision geordnetes Operation-Log. Jeder akzeptierte Schreibvorgang erhöht eine monotone Revision und hängt einen Eintrag an. Dieses Log ist das Substrat, mit dem Replicas und neue Partition-Owner aufholen, ohne Schreibzugriffe einzufrieren: Ein Consumer, der die zuletzt gesehene Revision kennt, kann ab diesem Punkt vorwärts abspielen und zur aktuellen Wahrheit konvergieren.

Diese Eigenschaft — ab einer bekannten Revision vorwärts abspielen — ist das gesamte Fundament eines WAN-Replikators. Ein Change Feed ist nichts weiter als ein langlebiger Leser dieses Logs, der Einträge in Revisionsreihenfolge streamt und sich merkt, wo er aufgehört hat. Das, was er sich merkt, ist ein Cursor: die höchste Revision, die er dauerhaft angewendet hat, pro Segment.

Weil das Log nach Revision geordnet ist und das Abspielen an einem von Ihnen gewählten Punkt beginnt, hat der Feed die beiden Eigenschaften, die eine WAN-Verbindung verlangt:

  1. Wiederaufnehmbar. Die Verbindung wird abbrechen — das ist die definierende Tatsache eines WAN. Kommt sie zurück, verbindet sich der Follower neu und fragt nach allem nach seinem gespeicherten Cursor. Keine vollständige Neukopie, keine Koordination, einfach „bei Revision N fortsetzen".
  2. Idempotent beim Abspielen. Einen Eintrag nur anwenden, wenn seine Revision größer ist als die zuletzt für dieses Segment angewendete. Liefert ein Reconnect das Ende erneut aus, überspringt der Follower, was er schon hat. Doppelte Zustellung wird zum No-op, wodurch der Feed At-least-once sein darf, statt Exactly-once sein zu müssen.

Das Muster, von Anfang bis Ende​

Zwei Cluster. Nennen wir sie A (die Quelle) und B (der Follower). Jeder ist ein gewöhnlicher, unabhängiger Zaris-Cluster, der nichts von den Interna des anderen weiß.

In Cluster B betreiben Sie einen Replikations-Follower — einen Client, der den Change Feed von A konsumiert und die Operationen lokal in B schreibt. Sein Zustand ist winzig und dauerhaft: ein Cursor pro Segment. Die Schleife ist unspektakulär, und das ist der Punkt:

  1. Mit A verbinden, den Change Feed für die zu replizierenden Segmente (oder Key-Präfixe) anfordern, beginnend nach dem gespeicherten Cursor jedes Segments.
  2. Einträge in Revisionsreihenfolge empfangen. Jede Operation auf B anwenden — SET, DELETE, Expiry —, abgesichert durch die Revisionsprüfung, sodass eine erneute Zustellung harmlos ist.
  3. Den Cursor voranschreiten lassen und persistieren, sobald Einträge dauerhaft angewendet sind.
  4. Bei jeder Trennung zurück zu Schritt 1. Der Cursor ist, wo Sie aufgehört haben.

Alles, was der Follower braucht, um eine Unterbrechung zu überstehen, steckt in diesem Cursor. Es gibt keine verteilte Transaktion über das WAN, keine gemeinsame Mitgliedschaft, kein regionsübergreifendes Quorum. Ein Aussetzer der Verbindung kostet nichts außer etwas Aktualität.

Was Sie eintauschen: RPO ist nicht null, und das ist der Deal​

Diese Replikation ist asynchron. Der Follower hinkt der Quelle immer etwas hinterher — um so viele Revisionen, wie in A erzeugt, aber in B noch nicht angewendet wurden. Dieser Abstand ist Ihr Recovery Point Objective: Verschwindet Region A in diesem Augenblick, verlieren Sie, was B noch nicht erreicht hatte.

Das ist kein Fehler, für den man sich entschuldigen müsste; es ist der Kompromiss, den Sie ausdrücklich wählen. Die Alternative — Schreibzugriffe in A auf eine Bestätigung von B warten zu lassen, bevor sie committen — würde einen WAN-Round-Trip auf Ihren Schreibpfad legen und die niedrige Latenz opfern, die der ganze Grund ist, Zaris zu betreiben. Synchrone regionsübergreifende Replikation macht jeden Schreibvorgang zu einer transkontinentalen Anfrage. Das will fast niemand, sobald er die Latenzrechnung sieht.

Die ehrliche Formulierung lautet also: Sie wählen den RPO. Ein gut versorgter Follower auf einer gesunden Verbindung bleibt innerhalb weniger Revisionen — in der Praxis Staleness unter einer Sekunde. Eine gesättigte oder flatternde Verbindung lässt ihn weiter abdriften. Der Cursor macht die Drift begrenzt und sichtbar — Sie können genau messen, wie weit B zurückliegt, indem Sie seinen Cursor mit der aktuellen Revision von A vergleichen —, aber er macht sie nicht null. Dimensionieren Sie Ihre Toleranz entsprechend und alarmieren Sie bei Cursor-Lag, so wie Sie bei Replica-Lag innerhalb eines Clusters alarmieren würden.

Wenn die Quelle ein Failover durchführt, ist es dem Feed egal​

Hier zahlt sich der Aufbau auf dem Operation-Log ein zweites Mal aus. Innerhalb von Cluster A wird die Partition, die Ihr Feed liest, gelegentlich den Owner wechseln — ein Node stirbt, eine Replica wird befördert, eine Partition migriert. Das Log überlebt das: Es ist nach Revision geschlüsselt, und ein neuer Owner setzt dieselbe Revisionssequenz fort. Für den Follower ist ein Failover auf der Quellseite nur ein weiterer Reconnect. Er baut den Feed gegen die aktuelle Topologie von A neu auf und setzt bei seinem Cursor fort. Die Revisionen, die er schon angewendet hat, sind weiterhin die Revisionen, die er schon angewendet hat; die, die ihm fehlen, liegen weiterhin hinter dem Cursor. Der Follower muss nie verstehen, wer in A die Partition besitzt — nur, welche Revision er zuletzt gesehen hat.

Das ist der stille Grund, warum das Design mit Cursor über dem Log mehr wert ist als ein naives Relay, das „die Schreibzugriffe mitliest und weiterschickt". Ein Relay, das Live-Schreibzugriffe ohne dauerhafte, nach Revision geschlüsselte Position streamt, hat nichts, womit es fortsetzen könnte, wenn sich die Quelltopologie verschiebt. Das Log gibt dem Follower ein stabiles Koordinatensystem, das jeden einzelnen Owner überdauert.

Der Umfang ist eine Wahl, und Konflikte sind eine Grenze​

Zwei weitere Dinge sollten bewusst entschieden werden.

Datenumfang. Sie müssen nicht alles replizieren. Der Feed arbeitet pro Segment, und Sie können die Segmente oder Key-Präfixe auswählen, auf die es ankommt — einen bestimmten Tenant, einen bestimmten Datensatz, den Ausschnitt, den eine Disaster-Recovery-Region wirklich braucht. Weniger zu replizieren bedeutet weniger WAN-Traffic und einen kleineren Follower; alles zu replizieren ergibt ein vollständiges Warm-Standby. Entscheiden Sie bewusst statt per Default.

Richtung und Konflikte. Das obige Muster ist unidirektional: A ist die Quelle der Wahrheit, B ist ein überwiegend lesender Follower / Standby. Das ist der saubere Fall, und er hat kein Konfliktproblem — es gibt genau einen Schreiber pro Key. In dem Moment, in dem Sie es bidirektional machen — beide Regionen akzeptieren Schreibzugriffe auf dieselben Keys —, haben Sie nebenläufige Schreiber eingeführt, die durch ein WAN getrennt sind, und keine Menge an Change-Feed-Plumbing löst das für Sie. Nun schulden Sie eine Konfliktauflösungsrichtlinie: Last-Writer-Wins auf einer Uhr, der Sie vertrauen, Ownership pro Key, sodass die beiden Regionen nie denselben Key schreiben, oder eine Merge-Logik in der Anwendung. Active-Active ist ein reales und manchmal notwendiges Design, aber es ist eine Datenmodellierungs-Entscheidung, kein Transport-Feature. Greifen Sie zuerst zum unidirektionalen Modell; setzen Sie bidirektional nur ein, wenn Sie ausdrücklich entschieden haben, wie zwei Schreiber abgeglichen werden.

Die Form zum Merken​

Ein WAN ist kein langsames LAN; es ist ein anderes Medium, in dem Partitionen ein normales Ereignis sind und kein Notfall. Ein einzelner Cluster weigert sich korrekterweise, etwas anderes vorzutäuschen — seine Timer und sein Ownership-Protokoll sind darauf abgestimmt, eine lange Stille als Ausfall zu werten, weil sie das innerhalb eines Datacenters fast immer ist. Also strecken Sie den Cluster nicht. Sie betreiben zwei, jeder souverän in seinem eigenen Netzwerk, und verbinden sie mit dem einen Primitiv, das darauf ausgelegt ist, eine Verbindung zu tolerieren, die kommt und geht: ein nach Revision geordnetes Log, das Sie abspielen können, und ein Cursor, der sich merkt, wo Sie waren.

Unabhängigkeit ist die Entscheidung, die den Rest sicher macht. Der Change Feed ist nur das Rohr. Das Operation-Log hat Ihnen den Cursor bereits geliefert.