Zum Hauptinhalt springen

19 Posts getaggt mit ".NET"

Entwicklung verteilter Systeme und Infrastruktur in .NET.

Alle Tags anzeigen

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.

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.

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.

Die Worker-Pool-Obergrenze: Wenn ein Node bei 25 % CPU ein Plateau erreicht

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

Ein Node bei nahezu 25 % CPU mit flachem Durchsatz, weil der Dispatch-Worker-Pool die Obergrenze war

Hier ist ein Benchmark-Ergebnis, das wie ein Widerspruch aussieht. Ein Zaris-Node, der eine leselastige Workload bedient, erreicht seine Obergrenze bei knapp unter einer halben Million Ops/s und weigert sich zu steigen — fügen Sie mehr Client-Verbindungen und mehr Lastgeneratoren hinzu, die Zahl bewegt sich nicht. Die naheliegende Diagnose lautet „er ist gesättigt." Nur sagt das CPU-Diagramm etwas anderes: Der Node steht bei etwa einem Viertel seiner verfügbaren CPU. Drei von vier Kernen sind untätig, und der Durchsatz ist flach.

Das ist kein CPU-limitierter Node und — wenn Sie das sorgfältig ausgeschlossen haben — auch kein Client-limitierter Benchmark. Es ist eine dritte Sache, und zwar ein Klassiker: Der Worker-Pool für das Request-Dispatching war schmaler, als die Arbeit es wollte, sodass Requests in der Warteschlange standen, während Kerne Däumchen drehten.

Drop-in-Cache für ASP.NET Core: IDistributedCache und HybridCache auf Zaris

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

Drop-in-verteilter-Cache für ASP.NET Core auf Zaris

Die meisten Gründe, in einer .NET-App zu einem verteilten Cache zu greifen, nennen den Cache gar nicht beim Namen. Sie aktivieren Output Caching, und es verlangt ein IDistributedCache. Sie aktivieren Sessions, und die Middleware will einen Backing-Store. Sie schalten das Teilen von Data-Protection-Schlüsseln über Instanzen hinweg ein, und wieder braucht es einen Ort für Bytes, den jeder Node sieht. Der Anwendungscode ist gegen eine Abstraktion geschrieben; offen ist nur, welche Implementierung Sie beim Start registrieren.

Zaris beantwortet diese Frage mit drei kleinen NuGet-Paketen, die jeweils eine bestimmte ASP.NET-Core-Nahtstelle bedienen: ein schlichtes IDistributedCache, ein zweistufiges HybridCache und ein Session-Store. Sie registrieren beim Start eine Zeile, und der Rest Ihrer App — einschließlich Drittanbieter-Bibliotheken, die nur die Abstraktion kennen — cacht auf einem Zaris-Cluster, ohne dass sich der Code ändert, der den Cache nutzt. Dieser Beitrag geht alle drei durch und ist dabei bewusst klar darüber, wo jedes ehrlicherweise endet.

Die Steuerungsebene in Ihrer Shell: Einen Zaris-Cluster mit PowerShell betreiben

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

Einen Zaris-Cluster mit PowerShell bereitstellen und betreiben

Das meiste, was man über einen Datenspeicher liest, handelt vom Datenpfad — Lese- und Schreibvorgänge, das Wire-Protokoll, wie schnell es ist. Doch bevor das alles zählt, muss jemand das Ding aufsetzen: entscheiden, wie viele Partitionen es hat, wie viele Kopien jeder davon, wo die Ports liegen, es starten, bestätigen, dass es gesund hochgekommen ist, und es im Betrieb beobachten. Das ist die Steuerungsebene, und in Zaris lebt sie in PowerShell.

Dies ist eine Day-one-Anleitung für Operatoren. Wir legen einen Workspace an, stellen einen Store bereit, starten ihn und lesen dann seinen Live-Zustand zurück — alles aus dem Modul AdminShell. Keine Web-Konsole nötig, nichts von Hand bearbeitet. Und weil bei einer Operator-Geschichte Vertrauen der ganze Punkt ist, benennen wir klar die Entscheidungen, die New-ZrStore Ihnen vorlegt, und was jede tatsächlich kostet.

Derselbe Code, mit oder ohne Netzwerk: Der Embedded-Modus von Zaris

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

Zaris Embedded-In-Process-Modus

Die meisten verteilten Datenspeicher zwingen Sie, sich früh und endgültig zwischen zwei Welten zu entscheiden. Da ist der „echte" Store — ein Cluster, den Sie deployen, auf den Sie einen Client über das Netzwerk richten und mit dem Sie über ein Wire-Protokoll sprechen. Und da ist der „falsche" Store, zu dem Sie in Unit-Tests greifen: ein In-Memory-Dictionary, ein Mock, ein handgeschriebener Stub, der gerade genug der Schnittstelle implementiert, damit der Test kompiliert. Beide haben nichts gemeinsam. Ihr Test prüft das Verhalten des Stubs, nicht das des Stores, und die Lücke dazwischen ist genau der Ort, an dem sich Bugs verstecken.

Zaris hebt diese Entscheidung auf. Derselbe typisierte .NET-Client, der über TLS mit einem verteilten Cluster spricht, betreibt den Store auch eingebettet in Ihrem Prozess, ohne Socket, ohne Partition-Map und ohne Deployment — und zwar mit derselben Store-Engine, nicht mit einem Ersatz. Welchen von beiden Sie bekommen, wählen Sie mit einer einzigen Connection-String. Dieser Beitrag erklärt, wie das funktioniert, wofür es sich wirklich eignet und wo die harte Grenze verläuft, an der der Embedded-Modus endet und ein Cluster beginnt.

Retries, die Sie nicht schreiben müssen: Ownership-Churn im Zaris-Client überstehen

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

Transparenter Retry und Reroute bei Ownership-Churn

Hier eine Tatsache über jeden partitionierten Key/Value-Store, die die glänzenden Diagramme gern auslassen: Der Node, der Ihren Schlüssel besitzt, wird wechseln. Ein Node fällt aus, und seine Partitionen gehen per Failover auf ihre Replikate über. Sie skalieren aus, und Partitionen wandern auf das neue Mitglied. Ein bevorzugter Primary kehrt zurück, und der Besitz fällt an ihn zurück. Jedes dieser Ereignisse bedeutet: Der Node, mit dem Ihr Client vor einer Millisekunde noch zufrieden sprach, ist jetzt für einen Teil der Schlüssel der falsche.

Die naive Client-Erfahrung in diesem Moment ist hässlich: ein Verbindungsabbruch, ein generischer „Serverfehler" oder — schlimmer — ein Schreibvorgang, der auf einem Node landet, der nicht mehr der aktive Owner ist, und stillschweigend im Nichts verschwindet. Der Zaris-.NET-Client ist so gebaut, dass nichts davon Ihren Code erreicht. Ein Ownership-Wechsel wird zu einem typisierten Status, der Client routet um zum neuen Owner, und der Retry ist sowohl durch ein Versuchsbudget als auch durch eine Wanduhr-Deadline begrenzt. Im Normalfall dauert Ihr PutAsync einfach ein paar Millisekunden länger und liefert Ok. Dieser Beitrag beschreibt die Mechanik dahinter — und ebenso wichtig: die Fälle, die sie bewusst nicht überdeckt.

Allokationsfreie Hot Paths: Wie der Zaris-.NET-Client ValueTask nutzt

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

Der Zaris-.NET-Client nutzt ValueTask in seinen Hot-Path-Operationen

Meistens ist Task<T> genau der richtige Rückgabetyp für eine asynchrone Methode, und man sollte nicht zweimal darüber nachdenken. Es gibt aber einen bestimmten Ort, an dem er unbemerkt Kosten verursacht: ein Hot Path, der synchron abschließt und millionenfach aufgerufen wird.

Der Mechanismus: Task<T> ist ein Referenztyp. Jeder Aufruf, der einen zurückgibt, allokiert ein Task-Objekt auf dem Heap — selbst wenn die Operation synchron abschließt. Ein Cache-Treffer direkt aus einem lokalen Puffer, ein Awaitable, das bereits fertig ist: Nichts davon erfordert echte Asynchronität, dennoch zahlen Sie für ein Heap-Objekt, das das Ergebnis trägt. Eine Allokation ist nichts. Bei der Aufrufrate eines Clients mit hohem Durchsatz — einer engen Schleife mit Millionen kleiner Gets und Puts — ist das ein stetiger Strom kleiner Heap-Allokationen, und dieser Strom füttert den Garbage Collector. Mehr Gen-0-Collections, mehr CPU für GC statt für Ihre Arbeit, mehr Jitter bei den Tail-Latenzen.

Der Zaris-.NET-Client setzt genau dort an, wo es zählt, und nirgendwo sonst. Dieser Beitrag handelt von dieser Entscheidung: welche Methoden sich geändert haben, warum, welche Regeln Sie für den korrekten Einsatz befolgen müssen und — bewusst — welche Methoden wir unangetastet gelassen haben.

Große Werte, kleine Pausen: Large-Object-Chunking in Zaris

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

Large-Object-Chunking hält große Werte vom LOH fern

Es gibt eine Zahl, die sich jeder .NET-Performance-Ingenieur irgendwann merkt: 85.000 Bytes. Jede einzelne Allokation ab etwa dieser Größe landet nicht auf dem normalen Heap für kleine Objekte, sondern auf dem Large Object Heap (LOH). Der LOH ist ein anderes Tier. Er wird nur bei einer Gen-2-(vollständigen) Garbage Collection eingesammelt und standardmäßig nicht kompaktiert. Diese Kombination ist in Ordnung für einen großen Puffer, den man einmal allokiert und behält. Sie ist nicht in Ordnung für einen latenzarmen In-Memory-Store, der wiederholt große Werte schreibt und überschreibt.

Stellen Sie sich die Last vor: große serialisierte Blobs, umfangreiche JSON-Dokumente, Bilder, Protokoll-Payloads — gespeichert, aktualisiert, ersetzt, immer wieder. Jeder dieser großen Schreibvorgänge legt ein frisches Objekt auf dem LOH ab. Da der LOH nicht kompaktiert wird, verschmelzen freigegebene Bereiche nicht zu zusammenhängendem Speicher, sondern fragmentieren. Und weil jede Rückgewinnung eine vollständige Gen-2-Collection bedeutet, werden die Pausen länger und treten häufiger auf, genau dann, wenn der Working Set wächst. Für einen Store, dessen ganzes Versprechen niedrige Latenz ist, ist das die falsche Art von Überraschung.

Zaris liefert ein optionales Feature, das den LOH für solche Werte komplett umgeht. Es ist standardmäßig aus, und dieser Beitrag erklärt, was es tut, wie es funktioniert und — ebenso wichtig — wann Sie es einschalten sollten und wann nicht.