Sicherstellung der Zuverlässigkeit groß angelegter Plattformen

Der Betrieb einer skalierbaren verteilten Plattform erfordert ein Bekenntnis zur Zuverlässigkeit, um sicherzustellen, dass Kunden das haben, was sie brauchen, wenn sie es brauchen. Die Abhängigkeiten können recht komplex sein, insbesondere bei einer so großen Plattform wie Roblox. Der Aufbau zuverlässiger Dienste bedeutet, dass ein bestimmter Dienst unabhängig von der Komplexität und dem Status der Abhängigkeiten nicht unterbrochen wird (d. h. hochverfügbar ist), fehlerfrei läuft (d. h. hohe Qualität aufweist) und ohne Fehler arbeitet (d. h. fehlertolerant ist).
Warum Zuverlässigkeit wichtig ist
Unser Account-Identity-Team ist bestrebt, eine höhere Zuverlässigkeit zu erreichen, da die von uns entwickelten Compliance-Dienste Kernkomponenten der Plattform sind. Eine Verletzung der Compliance kann schwerwiegende Folgen haben. Die Kosten für die Blockierung des normalen Betriebs von Roblox sind sehr hoch, da zusätzliche Ressourcen für die Wiederherstellung nach einem Ausfall erforderlich sind und die Benutzererfahrung beeinträchtigt wird.
Der typische Ansatz zur Zuverlässigkeit konzentriert sich in erster Linie auf die Verfügbarkeit, doch in manchen Fällen werden Begriffe verwechselt und falsch verwendet. Die meisten Messungen der Verfügbarkeit beurteilen lediglich, ob Dienste laufen, während Aspekte wie Partitionstoleranz und Konsistenz manchmal vergessen oder missverstanden werden.
Gemäß dem CAP-Theorem kann jedes verteilte System nur zwei dieser drei Aspekte garantieren, daher opfern unsere Compliance-Dienste etwas Konsistenz, um hochverfügbar und partitionstolerant zu sein. Dennoch haben unsere Dienste nur wenig geopfert und Mechanismen gefunden, um mit den unten erläuterten angemessenen architektonischen Änderungen eine gute Konsistenz zu erreichen.
Der Prozess zur Erreichung einer höheren Zuverlässigkeit ist iterativ, wobei strenge Messungen mit kontinuierlicher Arbeit einhergehen, um Fehler zu verhindern, zu finden, zu erkennen und zu beheben, bevor Vorfälle auftreten. Unser Team hat in den folgenden Praktiken einen hohen Mehrwert erkannt:
- Richtige Messung – Schaffung umfassender Beobachtbarkeit hinsichtlich der Art und Weise, wie Qualität an Kunden geliefert wird und wie Abhängigkeiten uns Qualität liefern.
- Proaktive Vorausschau – Durchführung von Aktivitäten wie Architekturüberprüfungen und Risikobewertungen von Abhängigkeiten.
- Priorisierung der Korrektur – Größere Aufmerksamkeit auf die Lösung von Vorfallmeldungen für den Dienst und die mit unserem Dienst verbundenen Abhängigkeiten richten.
Der Aufbau einer höheren Zuverlässigkeit erfordert eine Kultur der Qualität. Unser Team investierte bereits in leistungsorientierte Entwicklung und weiß, dass der Erfolg eines Prozesses von dessen Umsetzung abhängt. Das Team hat diesen Prozess vollständig übernommen und die Praktiken als Standard angewendet. Das folgende Diagramm verdeutlicht die Komponenten des Prozesses:

Die Bedeutung der richtigen Messung
Bevor wir uns näher mit den Kennzahlen befassen, möchte ich kurz etwas zu den Service-Level-Messungen klarstellen.
- SLO (Service Level Objective) ist das Zuverlässigkeitsziel, das unser Team anstrebt (z. B. 99,999 %).
- SLI (Service Level Indicator) ist die in einem bestimmten Zeitraum erreichte Zuverlässigkeit (z. B. 99,975 % im vergangenen Februar).
- SLA (Service Level Agreement) ist die vereinbarte Zuverlässigkeit, die wir unseren Kunden in einem bestimmten Zeitraum liefern und von ihnen erwartet wird (z. B. 99,99 % pro Woche).
Der SLI sollte die Verfügbarkeit (keine unbeantworteten oder fehlenden Antworten), die Fehlertoleranz (keine Servicefehler) und die erreichte Qualität (keine unerwarteten Fehler) widerspiegeln. Daher haben wir unseren SLI als „Erfolgsquote“ der erfolgreichen Antworten im Vergleich zur Gesamtzahl der an einen Dienst gesendeten Anfragen definiert. Erfolgreiche Antworten sind jene Anfragen, die rechtzeitig und in der richtigen Form versendet wurden, d. h. es traten keine Verbindungs-, Service- oder unerwarteten Fehler auf.
Dieser SLI oder diese Erfolgsquote wird aus Sicht der Verbraucher (d. h. der Kunden) erfasst. Damit soll die tatsächliche End-to-End-Erfahrung gemessen werden, die unseren Verbrauchern geboten wird, damit wir sicher sein können, dass die SLAs eingehalten werden. Andernfalls würde ein falsches Gefühl der Zuverlässigkeit entstehen, das alle infrastrukturellen Probleme bei der Verbindung mit unseren Kunden außer Acht lässt. Ähnlich wie beim Verbraucher-SLI erfassen wir den Abhängigkeits-SLI, um potenzielle Risiken zu verfolgen. In der Praxis sollten alle Abhängigkeits-SLAs mit dem Service-SLA übereinstimmen, und es besteht eine direkte Abhängigkeit zwischen ihnen. Der Ausfall eines einzelnen bedeutet den Ausfall aller. Wir erfassen und melden auch Metriken aus dem Service selbst (d. h. dem Server), doch dies ist nicht die praktische Quelle für hohe Zuverlässigkeit.
Zusätzlich zu den SLIs erfasst jeder Build Qualitätsmetriken, die von unserem CI-Workflow gemeldet werden. Diese Vorgehensweise hilft dabei, Qualitätskontrollen (z. B. Codeabdeckung) konsequent durchzusetzen und andere aussagekräftige Metriken zu melden, wie die Einhaltung von Codierungsstandards und die statische Codeanalyse. Dieses Thema wurde bereits in einem anderen Artikel behandelt: „Building Microservices Driven by Performance“. Die gewissenhafte Einhaltung von Qualitätsstandards zahlt sich aus, wenn es um Zuverlässigkeit geht, denn je mehr wir in das Erreichen hervorragender Werte investieren, desto sicherer sind wir, dass das System unter widrigen Bedingungen nicht ausfällt.
Unser Team verfügt über zwei Dashboards. Das eine bietet einen vollständigen Überblick über sowohl die Consumers SLI als auch die Dependencies SLI. Das zweite zeigt alle Qualitätsmetriken an. Wir arbeiten daran, alles in einem einzigen Dashboard zusammenzufassen, sodass alle für uns wichtigen Aspekte konsolidiert und für die Berichterstattung zu jedem beliebigen Zeitpunkt bereit sind.
Ausfälle vorhersehen
Die Durchführung von Architekturüberprüfungen ist ein grundlegender Bestandteil der Zuverlässigkeit. Zunächst stellen wir fest, ob Redundanz vorhanden ist und ob der Dienst über die Mittel verfügt, um weiterzulaufen, wenn Abhängigkeiten ausfallen. Über die typischen Replikationskonzepte hinaus setzten die meisten unserer Dienste verbesserte Dual-Cache-Hydration-Techniken, duale Wiederherstellungsstrategien (wie lokale Failover-Warteschlangen) oder Strategien gegen Datenverlust (wie Transaktionsunterstützung) ein. Diese Themen sind umfangreich genug, um einen eigenen Blogbeitrag zu rechtfertigen, aber letztendlich lautet die beste Empfehlung, Konzepte zu implementieren, die Katastrophenszenarien berücksichtigen und Leistungseinbußen minimieren.
Ein weiterer wichtiger Aspekt, den es zu berücksichtigen gilt, ist alles, was die Konnektivität verbessern könnte. Das bedeutet, bei der geringen Latenz für Clients aggressiv vorzugehen und diese mithilfe von Cache-Control-Techniken, Sidecars und leistungsstarken Richtlinien für Timeouts, Circuit Breaker und Wiederholungsversuche auf sehr hohen Datenverkehr vorzubereiten. Diese Vorgehensweisen gelten für jeden Client, einschließlich Caches, Stores, Warteschlangen und voneinander abhängigen Clients in HTTP und gRPC. Es bedeutet auch, die Statusmeldungen der Dienste zu verbessern und zu verstehen, dass Zustandsprüfungen bei jeder Container-Orchestrierung eine wichtige Rolle spielen. Die meisten unserer Dienste geben im Rahmen des Feedbacks der Zustandsprüfung bessere Signale bei Leistungsbeeinträchtigungen aus und überprüfen, ob alle kritischen Komponenten funktionsfähig sind, bevor sie Statusmeldungen senden.
Die Aufteilung von Diensten in kritische und nicht-kritische Teile hat sich als nützlich erwiesen, um den Fokus auf die Funktionen zu legen, die am wichtigsten sind. Früher hatten wir Endpunkte, die nur für Administratoren bestimmt waren, im selben Dienst, und obwohl sie nicht oft genutzt wurden, wirkten sie sich auf die allgemeinen Latenzmetriken aus. Die Verlagerung in einen eigenen Dienst wirkte sich positiv auf alle Metriken aus.
Die Abhängigkeits-Risikobewertung ist ein wichtiges Werkzeug, um potenzielle Probleme mit Abhängigkeiten zu identifizieren. Das bedeutet, dass wir Abhängigkeiten mit niedrigem SLI identifizieren und eine SLA-Anpassung anstreben. Diese Abhängigkeiten erfordern während der Integrationsschritte besondere Aufmerksamkeit, daher nehmen wir uns zusätzliche Zeit für Benchmarks und Tests, um zu prüfen, ob die neuen Abhängigkeiten für unsere Pläne ausgereift genug sind. Ein gutes Beispiel ist die frühzeitige Einführung des Roblox Storage-as-a-Service. Die Integration dieses Dienstes erforderte das Erstellen von Bug-Tickets und regelmäßige Synchronisationsbesprechungen, um Erkenntnisse und Feedback auszutauschen. All diese Arbeit wird mit dem Tag „Zuverlässigkeit“ versehen, damit wir schnell die Quelle und die Prioritäten identifizieren können. Die Charakterisierung erfolgte so lange, bis wir sicher waren, dass die neue Abhängigkeit für uns bereit war. Diese zusätzliche Arbeit trug dazu bei, die Abhängigkeit auf das erforderliche Zuverlässigkeitsniveau zu bringen, das wir erwarten, wenn wir gemeinsam auf ein gemeinsames Ziel hinarbeiten.
Struktur ins Chaos bringen
Es ist nie wünschenswert, dass Vorfälle auftreten. Aber wenn sie passieren, gibt es wichtige Informationen zu sammeln und daraus zu lernen, um zuverlässiger zu werden. Unser Team verfügt über einen Team-Vorfallbericht, der über den üblichen unternehmensweiten Bericht hinausgeht, sodass wir uns auf alle Vorfälle konzentrieren, unabhängig vom Ausmaß ihrer Auswirkungen. Wir ermitteln die Grundursache und priorisieren alle Maßnahmen, um diese in Zukunft zu vermeiden. Im Rahmen dieses Berichts ziehen wir andere Teams hinzu, um Vorfälle bei Abhängigkeiten mit hoher Priorität zu beheben, sorgen für eine ordnungsgemäße Lösung, führen eine Retrospektive durch und suchen nach Mustern, die auf uns zutreffen könnten.
Das Team erstellt einen monatlichen Zuverlässigkeitsbericht pro Dienst, der alle hier erläuterten SLIs, alle Tickets, die wir aufgrund von Zuverlässigkeitsproblemen eröffnet haben, sowie alle möglichen Vorfälle im Zusammenhang mit dem Dienst enthält. Wir sind so daran gewöhnt, diese Berichte zu erstellen, dass der nächste logische Schritt darin besteht, ihre Erstellung zu automatisieren. Die Durchführung dieser regelmäßigen Tätigkeit ist wichtig und erinnert uns daran, dass die Zuverlässigkeit in unserer Entwicklung ständig verfolgt und berücksichtigt wird.

Unsere Instrumente umfassen benutzerdefinierte Metriken und verbesserte Warnmeldungen, damit wir so schnell wie möglich benachrichtigt werden, wenn bekannte und erwartete Probleme auftreten. Alle Warnmeldungen, einschließlich Fehlalarme, werden wöchentlich überprüft. An dieser Stelle ist es wichtig, die gesamte Dokumentation zu optimieren, damit unsere Nutzer wissen, was sie zu erwarten haben, wenn Warnmeldungen ausgelöst werden und Fehler auftreten, und damit jeder weiß, was zu tun ist (z. B. werden Playbooks und Integrationsrichtlinien regelmäßig abgestimmt und aktualisiert).
Letztendlich ist die Verankerung von Qualität in unserer Unternehmenskultur der entscheidende Faktor für eine höhere Zuverlässigkeit. Wir können beobachten, wie sich diese in unserer täglichen Arbeit angewandten Praktiken bereits auszahlen. Unser Team ist von Zuverlässigkeit besessen, und das ist unsere wichtigste Errungenschaft. Wir haben unser Bewusstsein dafür geschärft, welche Auswirkungen potenzielle Fehler haben könnten und wann sie auftreten könnten. Dienste, die diese Praktiken umgesetzt haben, haben ihre SLOs und SLAs durchweg erreicht. Die Zuverlässigkeitsberichte, die uns helfen, all unsere Arbeit nachzuverfolgen, sind ein Beweis für die Leistung unseres Teams und stellen unschätzbare Erkenntnisse dar, die andere Teams informieren und beeinflussen. So durchdringt die Kultur der Zuverlässigkeit alle Komponenten unserer Plattform.
Der Weg zu höherer Zuverlässigkeit ist kein einfacher, aber er ist notwendig, wenn man eine vertrauenswürdige Plattform aufbauen will, die die Art und Weise, wie Menschen zusammenkommen, neu definiert.
Alberto ist Principal Software Engineer im Account Identity-Team bei Roblox. Er ist seit langem in der Spieleindustrie tätig und hat an vielen AAA-Spieltiteln und Social-Media-Plattformen mitgearbeitet, wobei sein Schwerpunkt auf hochskalierbaren Architekturen liegt. Jetzt unterstützt er Roblox dabei, Wachstum und Reife zu erreichen, indem er bewährte Entwicklungspraktiken anwendet.


