Zum Hauptinhalt springen

Viele Keys, wenige Round-Trips: Wie Zaris eine Bulk-Operation per Fan-out verteilt

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

Eine Bulk-Operation per Fan-out an die besitzenden Nodes verteilen

Fragen Sie einen verteilten Store nach einem Key, wird der Aufwand von einer einzigen Zahl dominiert: dem Round-Trip zum Node, der ihn besitzt. Der Lookup selbst dauert Nanosekunden; das Netzwerk Mikrosekunden. Fragen Sie nun nach tausend Keys. Wenn Sie in einer Schleife tausendmal Get aufrufen, zahlen Sie diesen Round-Trip tausendmal, seriell, und der Store verbringt fast die gesamte Wanduhrzeit untätig mit dem Warten auf Pakete. Die Keys könnten auf vier Nodes verteilt sein, die parallel hätten antworten können — aber eine naive Schleife gibt ihnen nie die Chance.

Zaris hat drei Bulk-APIs — GetManyAsync, PutManyAsync, DeleteManyAsync — und darunter ein gemischtes ExecuteBatchAsync, und ihr ganzer Daseinszweck ist, „tausend Keys" in „eine Anfrage pro besitzendem Node, parallel abgesetzt" zu verwandeln. Dieser Beitrag ist der Fan-out-Pfad von Anfang bis Ende: wie der Client Keys nach Owner in Buckets sortiert, warum alle vier APIs auf eine einzige Engine zusammenfallen, wie ein Index-Feld die Antworten durch zwei Schichten von Nebenläufigkeit in Reihenfolge hält und warum — anders als bei einer Multi-Key-Transaktion — eine Bulk-Operation pro Item arbeitet und ein Teilerfolg normal ist.

Ein Hop zum Owner: Wie der Zaris-Client einen Key routet

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

Wie der Zaris-Client einen Key zu seinem Owner routet

Es gibt zwei ehrliche Wege, einen Client für einen verteilten Cache zu bauen. Der erste: irgendeinen Node wählen, ihm die Anfrage senden und den Cluster sie intern an den Besitzer des Keys weiterleiten lassen — einfach für den Client, aber jede Anfrage, die auf dem falschen Node landet, zahlt für einen zusätzlichen Netzwerk-Hop innerhalb des Clusters. Der zweite: den Client klug genug machen, um zu wissen, welcher Node den Key besitzt, bevor er irgendetwas sendet, sodass der erste Socket, den er berührt, derjenige ist, der tatsächlich antworten kann. Zaris geht den zweiten Weg. Ein GET verlässt Ihren Prozess bereits an den Node adressiert, der den Key hält — kein Proxy, kein Weiterleitungs-Hop, kein Koordinator in der Mitte.

Dieser Beitrag ist der Blick ins Innere, wie das funktioniert: die Funktion, die einen Key in einen Owner verwandelt, die Lookup-Tabelle, die der Client im Speicher hält, und — der Teil, den man leicht falsch macht — wie der Client korrekt bleibt, wenn sich das Ownership bewegt, während Ihr Traffic unterwegs ist.

Ein Owner, der neu startet, hat kein Recht auf seine eigene Leere

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

Einen neu gestarteten Owner aus seinen Replicas wiederaufbauen

Ein Failover deckt den Moment ab, in dem ein Owner stirbt: Der Cluster wählt einen Überlebenden und bedient weiter. Eine geplante Migration deckt das absichtliche Verschieben der Partition eines gesunden Owners ab. Dieser Beitrag handelt vom dritten Akt, dem, der nach dem Ende der Sirenen abläuft — der ursprüngliche Owner-Prozess startet neu, tritt dem Cluster wieder bei und will seine Partition zurück. Sein Store, der nichts auf der Platte und alles im Speicher hält, kam leer zurück. Die Daten, für die er verantwortlich ist, leben noch, aber auf den Replicas, die weiter bedient haben, während er ausgefallen war.

Das gesamte Korrektheitsproblem steckt in einem Satz: Dem neu gestarteten Owner darf nicht erlaubt werden, seiner eigenen Leere zu trauen. Ein leerer Store sieht genau so aus wie ein Store, den jemand bewusst geleert hat. Nimmt der Node an „ich bin der Owner und habe keine Daten, also ist diese Partition leer", wird er bereitwillig NotFound für Millionen lebender Keys ausliefern und, schlimmer, seinen leeren Zustand zurück auf die Replicas fließen lassen und sie löschen. Dieser Beitrag geht die fünf kleinen, reinen Policies durch, mit denen Zaris sicherstellt, dass das nie geschieht.

Wie ein Zaris-Cluster hochfährt: Mitgliedschaft, die Map und durch Konvergenz gesteuerte Readiness

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

Wie ein Zaris-Cluster hochfährt: Mitgliedschaft, die Map und durch Konvergenz gesteuerte Readiness

Die meisten Texte über verteilte Stores handeln vom Normalbetrieb — Lesen, Schreiben, Replikation — oder von den dramatischen Ausfällen: einem Failover, wenn ein Owner stirbt, oder einem Cut-over, wenn eine Partition umzieht. Doch es gibt einen leiseren Moment, der darüber entscheidet, ob all das funktioniert: der Moment, in dem der Cluster hochfährt. Bevor auch nur ein Key geroutet werden kann, müssen sich die Nodes einigen, wer zum Cluster gehört, wem welcher Ausschnitt des Keyspace gehört und — der Schritt, den die meisten Systeme falsch machen — ob es überhaupt sicher ist, mit dem Bedienen zu beginnen.

Dieser Beitrag folgt dieser Boot-Sequenz in der Reihenfolge, in der Zaris sie tatsächlich ausführt: Mitgliedschaft, Partition-Map und die Readiness-Gates, die zwischen „der Prozess läuft" und „der Node nimmt Traffic an" stehen. Außerdem ziehen wir ehrlich eine Grenze zwischen dem, was Zaris selbst implementiert, und dem, was es von der zugrunde liegenden Cluster-Laufzeit erbt, auf der es aufbaut.

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.

Der Latenzboden: Eine Mikrosekunde Arbeit in zehn Mikrosekunden Installation

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

Eine Mikrosekunde Arbeit in zehn Mikrosekunden Installation

Hier ist eine Zahl, die verändert, wie man über einen Key/Value-Store denkt. Wenn wir den In-Memory-Store von Zaris isolieren und ihm Operationen zuführen, ohne dass etwas anderes im Weg ist — kein Socket, keine Serialisierung, kein Routing —, bedient ein einzelner Thread mehr als eine Million Lesezugriffe pro Sekunde und skaliert sauber auf mehr als acht Millionen über acht Threads. Der Store erledigt die eigentliche Arbeit eines GET also in unter zwei Mikrosekunden und ist ausdrücklich nicht der Engpass.

Und doch erreicht ein vollständiges GET über eine echte TCP-Verbindung auf derselben Maschine ein Plateau bei einigen Hunderttausend pro Sekunde. Der Store kann die Arbeit fünfmal schneller erledigen, als der Server sie liefern kann. Die interessante Performance-Frage zu Zaris lautet also nicht „Wie schnell ist der Store?" Sondern genau umgekehrt: Wohin geht die ganze Zeit zwischen Socket und Store? Dieser Beitrag ist die Antwort, Operation für Operation nachverfolgt, denn die Lücke zwischen 1,7 Mikrosekunden Logik und 10 bis 13 Mikrosekunden Server-CPU pro Operation ist kein Rauschen — sie ist der Latenzboden, und fast alles davon ist Installation.

Eine Partition ohne Datenverlust migrieren: das Cut-over-Protokoll

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

Das Zaris-Partition-Cut-over-Protokoll

Ein Failover ist das, was passiert, wenn ein Owner stirbt — der Cluster hat Sekunden, um einen Überlebenden zu wählen, und die Partition-Map bewegt sich nie. Migration ist der ruhigere, gefährlichere Cousin: Der Owner ist völlig gesund, und Sie wollen eine Partition absichtlich von ihm wegbewegen — um den Cluster zu vergrößern, neu zu balancieren oder einen Node zu leeren, den Sie außer Dienst stellen wollen. Nichts ist ausgefallen, und genau deshalb liegt die Latte höher. Ein Failover darf im schlimmsten Fall einige ehrlich verlorene Schreibzugriffe am Ende in Kauf nehmen; eine geplante Migration nicht. Wenn das Verschieben einer Partition von Node A zu Node B einen einzigen bestätigten Schreibzugriff verlieren kann, ist Migration kein Feature, sondern eine Belastung.

Dieser Beitrag geht das Cut-over-Protokoll durch, das Zaris tatsächlich ausführt, in dieser Reihenfolge, und verbringt die meiste Zeit bei dem einen Schritt, den alle falsch machen: dem Moment, in dem die Quelle schließlich „frei" ist, ihre Kopie zu verwerfen. Dieser Schritt ist eine geladene Waffe, und das Interessante an der Ingenieursarbeit ist die Sicherung daran.

Einen Keyspace scannen, der nicht stillhält: Wie SCAN einen Cluster durchläuft

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

Viele sortierte Segmente, zusammengeführt zu einem geordneten, seitenweisen Strom

Ein einzelnes GET auf einen Key ist leicht zu durchdenken: den Key hashen, den Owner finden, genau diesen einen Node fragen. SCAN ist die gegenteilige Art von Operation. Es hat keinen Key — es stellt eine Frage über den gesamten Keyspace: Gib mir jeden Key in diesem Bereich, Seite für Seite. Und in einem partitionierten, replizierten Store ist dieser Keyspace nicht eine Sache an einem Ort. Er ist über 256 Segmente verteilt, die auf so viele Nodes entfallen, wie Sie betreiben, wobei jeder Node nur die Ausschnitte hält, die er gerade besitzt, und während Sie iterieren, wird in die Gesamtheit geschrieben. Es gibt keinen globalen sortierten Index zum Durchlaufen. Woher kommt also die Ordnung, worauf zeigt ein Resume-Token genau, und was passiert mit Ihrem Scan, wenn mittendrin ein Key geschrieben wird — oder eine Partition umzieht?

Dieser Beitrag folgt einem SCAN durch die drei Schichten, die ihn tatsächlich bedienen: ein einzelnes Segment, ein einzelner Node und der Client, der die Nodes zusammenfügt. Jede Schicht löst ein Teilstück des Problems, und die ehrlichen Grenzen der Operation ergeben sich daraus, wie diese Teile zusammenpassen.

Die Speicherobergrenze: Warum ein Zaris-Node nie unbegrenzt wächst

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

Ein Zaris-Node hält die Speichernutzung in einem ruhigen Band unterhalb seiner Obergrenze und führt bei 80 % eine Eviction bis hinunter auf 70 % durch, sodass Schreibzugriffe das Limit nie überschreiten

Hier ist eine Frage, die einfach klingt und es nicht ist: Was passiert, wenn einem In-Memory-Store der Speicher ausgeht?

Die naive Antwort lautet „er stürzt ab" — der Prozess wächst, bis das Betriebssystem ihn beendet. Die etwas weniger naive Antwort lautet „er lehnt Schreibzugriffe ab". Zaris tut standardmäßig keines von beidem. Ein Zaris-Node ist ein In-Memory-Store, aber bewusst kein unbegrenzter: Jeder Node ist ein begrenzter Puffer mit einer harten Obergrenze, und wenn sich die Daten, die er besitzt, dieser Obergrenze nähern, entfernt der Node seine kältesten Daten, um Platz zu schaffen. Schreibzugriffe gelingen weiter; das Limit hält; der Prozess bläht sich nicht auf. Der Node verhält sich wie ein Cache, denn darunter ist er einer.

Was über die Leitung geht: Die Anatomie eines Zaris-Records

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

Die Anatomie eines gespeicherten Zaris-Records

Das meiste von dem, was Zaris gut macht — einen Schreibzugriff auf ein Backup replizieren, eine hinterherhinkende Replica aufholen lassen, eine Partition auf einen neuen Owner umschalten —, läuft auf eine stille Frage hinaus: Was genau ist die Dateneinheit, die zwischen Nodes wandert? Stimmt diese Einheit, ist Konvergenz fast langweilig. Lässt sie ein Feld weg oder trägt das falsche, bekommen Sie einen wiederauferstandenen gelöschten Key, eine Replica, die Veraltetes nicht von Frischem unterscheiden kann, oder die Buchführung eines Nodes sickert auf einen anderen.

In Zaris ist diese Einheit ein einziger Typ — StoredRecord — und es lohnt sich, ihn Feld für Feld auseinanderzunehmen, denn jede Entscheidung darin ist tragend. Dieser Beitrag ist die Anatomie: was im Record steckt, wie er auf der Leitung gerahmt wird, warum ein Löschvorgang als Record statt als Abwesenheit reist, warum es zwei verschiedene Versionsnummern gibt und — genauso wichtig — was bewusst auf jedem Node zurückbleibt und die Leitung nie überquert.