Im letzten Beitrag haben wir das Plateau der Stabilität, die erste Zwischenstation auf dem Weg zur Resilienz, erörtert – wozu es gut ist, wo seine Grenzen liegen und warum es so beliebt ist.
In diesem Beitrag werden wir die 100-%-Verfügbarkeitsfalle, die ich am Ende des letzten Beitrags vorgestellt habe, ausführlicher erörtern und uns erneut mit dem Thema Verfügbarkeit befassen. Wir werden auch die Erkenntnisse diskutieren, die notwendig sind, um die Reise zum nächsten Plateau fortzusetzen.
Die 100-%-Verfügbarkeitsfalle
Die 100 %-ige Verfügbarkeitsfalle habe ich bereits in einem früheren Blogbeitrag ausführlich besprochen. Daher fasse ich mich hier kurz.
Die 100-%-Verfügbarkeitsfalle ist der (immer noch weit verbreitete) Trugschluss, dass IT-Systeme zu 100 % der Zeit verfügbar sind.
Es ist der Nachfolger der „Ops ist für Verfügbarkeit verantwortlich“-Mentalität. Es ist die implizite Annahme, dass alle Teile der Systemlandschaft, für die man selbst nicht verantwortlich ist, nie und nimmer ausfallen werden.
Natürlich würde jede(r), wenn er bzw. sie direkt gefragt würde, zustimmen, dass alle Teile der Systemlandschaft ausfallen können und dass der Zeitpunkt des Ausfalls in der Regel nicht vorhergesagt werden kann. In der Praxis wird dieses theoretische Wissen jedoch weitgehend ignoriert. Nur selten werden Designs und Implementierungen erstellt, die mögliche Ausfälle anderer Systemteile berücksichtigen wie z. B.:
- Betriebssysteme
- Scheduler (wie z. B. Kubernetes) u. ä.
- Router
- Switches
- Message Busses
- Datenbanken
- Service Meshes
- Cloud-Dienste
und insbesondere andere Anwendungen und Dienste.
Bei der Konzeption und Implementierung wird implizit davon ausgegangen, dass alles, was über den Rahmen der eigenen Anwendung hinausgeht, jederzeit verfügbar ist. Wir können es überall sehen:
- Das Senden von Nachrichten oder Events an einen Message Bus wird als ausreichend angesehen, um die garantierte und korrekte Verarbeitung der Nachrichten bzw. Events zu gewährleisten.
- Das Erstellen von Anwendungen, die beim Start versuchen, Verbindungen zu allen Systemen herzustellen, mit denen sie zusammenarbeiten. Sollte eines der Systeme nicht verfügbar sein, stürzt die Anwendung einfach mit einer Fehlermeldung ab, da diese Situation nicht erwartet wird. [1]
- Das blinde Verlassen auf die perfekte Verfügbarkeit der Dienste eines Cloud-Anbieters. Als z. B. Kinesis bei AWS einmal ausfiel, fielen die Anwendungen der meisten Unternehmen, die Kinesis nutzen, ebenfalls aus, weil sie nicht mit Blick auf einen möglichen Ausfall von Kinesis (oder eines anderen AWS-Dienstes) entwickelt wurden. All diese Anwendungen gingen blind davon aus, dass Kinesis niemals ausfallen würde, auch wenn das SLA von Kinesis etwas anderes besagte.
- Und so weiter …
Mein persönliches Erklärungsmodell für dieses weit verbreitete Verhalten ist, dass es sich um eine Verallgemeinerung der tief verwurzelten „Ops ist für Verfügbarkeit verantwortlich“-Mentalität handelt. Es scheint, dass die „Problem Anderer Leute“-Einstellung (auch bekannt als P.A.L.) in Bezug auf die Verfügbarkeit eines nicht von einem selbst betreuten Systemteils sehr oft zu einer impliziten Annahme von 100 % Verfügbarkeit führt (selbst wenn die verantwortliche Partei in ihrem SLA deutlich macht, dass sie keine 100 % Verfügbarkeit garantiert).
Die 100-%-Verfügbarkeitsfalle führt sehr oft auch zu einer Art implizitem „Induktionsbeweis“ bezüglich Verfügbarkeit:
- Alles außer den Systemteilen, für die ich verantwortlich bin, ist zu 100 % verfügbar.
- Wenn ich also Ausfälle in meinen Teilen mit Hilfe der anerkannten Redundanz-, Failover- und Überlastvermeidungsmethoden (siehe die Beschreibung des Stabilitätsplateaus im vorherigen Beitrag) vermeide, ist alles zu 100 % verfügbar.
Der vielleicht faszinierendste Aspekt der 100 %igen Verfügbarkeitsfalle ist jedoch, dass sie allen anerkannten und bewährten Ingenieurspraktiken völlig widerspricht. Pat Helland und Dave Campbell begannen ihr großartiges Paper „Building on Quicksand“ mit dem Satz:
Zuverlässige Systeme wurden schon immer aus unzuverlässigen Komponenten gebaut.
Das heißt, wir rechnen damit, dass die verwendeten Komponenten ausfallen können und entwerfen und implementieren unsere Systeme so, dass sie trotz des Ausfalls der verwendeten Komponenten zuverlässig funktionieren. Zumindest ist dies die akzeptierte und bewährte Ingenieurspraxis.
Außer bei der Entwicklung von Unternehmenssoftware!
Bei der Entwicklung von Unternehmenssoftware neigen wir dazu, diese Praxis auf den Kopf zu stellen:
Bei der Entwicklung von Unternehmenssoftware bauen wir unzuverlässige Systeme (Anwendungen und Dienste) und erwarten von den verwendeten Komponenten (Infrastruktur und Middleware), dass sie das Gesamtsystem zuverlässig machen.
Dies ist das genaue Gegenteil davon, wie zuverlässige Systeme aufgebaut werden – eine Auswirkung der 100-%-Verfügbarkeitsfalle, die immer noch sehr weit verbreitet ist.
Das ist der Grund, warum so viele Unternehmen auf dem Plateau der Stabilität feststecken – einfach weil sie die 100-%-Verfügbarkeitsfalle noch nicht überwunden haben.
Die zweite Erkenntnis
Die 100-%-Verfügbarkeitsfalle ist jedoch eine Sackgasse in Bezug auf die Ausfallsicherheit, denn wie Michael Nygard in einem seiner Blog-Beiträge schreibt:
Kontinuierliche Teilausfälle sind der Normalzustand.
Heutige komplexe und hochgradig vernetzte Systemlandschaften laufen fast immer in einer Art degradiertem Modus, d.h. fast immer funktionieren einige Teile davon nicht wie erwartet.
Und Werner Vogels, der CTO von Amazon, benutzte die folgende, nur teilweise scherzhafte Definition, um verteilte Systeme zu beschreiben:
Alles kaputt. Andauernd. (Everything fails, all the time.)
Beide wissen viel über verteilte Systeme und den Betrieb großer, komplexer, verteilter und stark vernetzter Systemlandschaften. Ihre Erkenntnisse sind die gleichen:
Ausfälle sind unvermeidlich.
Im Grunde ist diese Erkenntnis nicht neu und nicht auf verteilte Systeme beschränkt. Michael Nygard z. B. macht in seinem oben erwähnten Blog-Beitrag deutlich, dass Ausfälle bei allen Arten von Systemen etwas ganz Normales sind.
Bis zum Plateau der Stabilität haben die Menschen jedoch immer noch ihre Scheuklappen auf und versuchen, die Tatsache zu ignorieren, dass Fehler unvermeidlich sind.
Daher besteht die zweite notwendige Erkenntnis darin, zu akzeptieren, dass Fehler unvermeidlich sind, was uns weg von der Vermeidung von Ausfällen hin zum Akzeptieren und sinnbildlichen "Umarmen" von Ausfällen führt.
Verfügbarkeit neu bewertet
Was bedeutet das für Verfügbarkeit? Wie können wir Verfügbarkeit maximieren, wenn Ausfälle unvermeidlich sind?
Um diese Fragen zu beantworten, müssen wir kurz zur Formel für Verfügbarkeit zurückkehren:
Verfügbarkeit := MTTF / (MTTF + MTTR)
mit
- MTTF: Mean Time To Failure (Durchschnittliche Zeit bis zum Ausfall), die durchschnittliche Zeit vom Beginn des Normalbetriebs bis zum Ausfall des betrachteten Systems, d.h. bis zum Auftreten eines von der Spezifikation abweichenden Verhaltens.
- MTTR: Mean Time To Recovery (Durchschnittliche Zeit bis zur Wiederherstellung), die durchschnittliche Zeit nach einem Ausfall, bis das betrachtete System wieder den normalen Betrieb aufnimmt, d. h. wie spezifiziert arbeitet.
Wenn wir uns diese Formel ansehen, können wir einige Beobachtungen machen:
- Der Nenner beschreibt die Gesamtzeit, während der Zähler die Zeit angibt, in der das System wie vorgesehen funktioniert, sprich der Bruch beschreibt den Anteil der Zeit, in der das System wie vorgesehen funktioniert.
- MTTF und MTTR sind beide positive Zahlen. Theoretisch könnten beide auch 0 sein, aber in der Praxis können wir davon ausgehen, dass beide Zahlen positiv sind, solange das betrachtete System überhaupt in der Lage ist, spezifikationskonform zu arbeiten (also MTTF ist nicht 0 ist).
- Aus der Tatsache, dass MTTF und MTTR beide positive Zahlen sind, können wir schließen, dass der Nenner immer größer ist als der Zähler, d. h., der Wert des Bruches ist eine Zahl zwischen 0 und 1 (beides in der Praxis ausgeschlossen) - oder zwischen 0 % und 100 %, wenn du Prozentsätze lieber magst.
- Je größer MTTF im Vergleich zu MTTR ist, desto näher liegt die Verfügbarkeit bei 1 (oder 100 %).
Letztendlich geht es uns darum, dass die Verfügbarkeit so nahe wie möglich an 1 (oder 100 %) herankommen soll.
Auf dem Plateau der Stabilität versuchen die handelnden Personen, Ausfälle zu vermeiden. Sie versuchen, MTTF so groß zu machen, dass MTTR keine Rolle mehr spielt. Wenn du sie fragen würdest, würden sie wahrscheinlich nicht sagen, dass sie versuchen, MTTF unendlich groß zu machen, sondern dass sie versuchen, MTTF hinreichend groß zu machen. [2]
Eine solche „hinreichend große“ MTTF könnte zum Beispiel 1.000.000 Stunden betragen. Dies würde eine erwartete Verfügbarkeit von mehr als 100 Jahren bedeuten. Wenn du davon ausgehst, dass nicht du, sondern deine Nachkommen mit einem Ausfall konfrontiert werden, ist dir MTTR eigentlich egal - einfach weil du keinen Ausfall erwartest. Es spielt keine Rolle, ob MTTR eine Stunde, einen Tag oder eine Woche beträgt. Selbst wenn MTTR eine Woche betragen würde, wäre die Gesamtverfügbarkeit immer noch höher als 99,98 %.
Da aber die MTTF als so hoch angenommen wird, denkt niemand über 99,98 % nach, sondern alle erwarten implizit, dass das System niemals ausfällt. Willkommen in der 100-%-Verfügbarkeitsfalle!
Wenn wir jedoch akzeptieren, dass Ausfälle unvermeidlich sind, müssen wir auch akzeptieren, dass MTTF für unser System eine Obergrenze hat. Wir können MTTF nicht willkürlich erhöhen. Wir kennen die Obergrenze nicht genau, aber höchstwahrscheinlich liegt sie näher bei 1.000 Stunden (das sind etwas mehr als 40 Tage) als bei 1.000.000 Stunden.
Wenn wir für unser System eine MTTF von 1.000 Stunden annehmen, wäre selbst eine MTTR von „nur“ einem Tag ein Problem, da dies eine Gesamtverfügbarkeit von ca. 97,5 % bedeuten würde, was für die meisten Szenarien zu wenig ist. Eine MTTR von 1.000 Stunden macht auch deutlich, dass Ausfälle nicht unsere Nachkommen treffen werden, sondern uns!
Das bedeutet, dass es keine Option ist, einfach zu versuchen, MTTF immer weiter zu erhöhen und dabei MTTR zu ignorieren, wenn wir akzeptieren, dass Ausfälle unvermeidlich sind und MTTF daher eine Obergrenze hat, die wir einfach nicht überwinden können. Wir sollten immer noch versuchen, eine gute MTTF zu erreichen, aber um unsere Gesamtverfügbarkeit weiter zu erhöhen, müssen wir auch unsere MTTR im Auge behalten und versuchen, sie zu verringern.
Wenn wir in unserem Beispiel MTTR von einem Tag auf eine Stunde senken, würde die Verfügbarkeit auf 99,9 % steigen (unter der Annahme einer MTTF von 1.000 Stunden), was sich wesentlich besser anhört als 97,5 %.
Zusammengefasst: Wenn wir verstehen, dass Ausfälle unvermeidlich sind, erkennen wir auch, dass MTTF eine Obergrenze hat und nicht beliebig erhöht werden kann. Um die gewünschte hohe Verfügbarkeit zu erreichen, müssen wir also zusätzlich MTTR reduzieren.
Die dritte Erkenntnis
Mit dieser Einsicht denken wir dann über die möglichen Arten von Fehlern nach, die uns treffen könnten, und darüber, wie wir sie schnell beheben können, d. h. wie wir unsere MTTR verringern können [3]. Dies führt unmittelbar zur dritten Erkenntnis:
Es gibt andere Fehlerarten als Abstürze und Überlastsituationen.
Auf dem Plateau der Stabilität neigt man dazu, sich auf Abstürze und Überlastsituation zu konzentrieren und versucht, diese mit allen Mitteln zu vermeiden, in der Annahme, dass dies ausreicht, um die gewünschte Verfügbarkeit zu erreichen.
Wenn man akzeptiert, dass Ausfälle unvermeidlich sind, fängt man an, sich die Fehler genauer anzuschauen, die das eigene System heimsuchen. Dann erkennt man sofort eine ganze Reihe von Fehlertypen, die nur darauf warten, die Robustheit deines Systems herauszufordern, wie z. B.:
- Absturzfehler
- Überlastfehler
- Auslassungsfehler
- Zeitfehler (Latenzen)
- Antwortfehler
- Byzantinische Fehler
- Software-Fehler
- Konfigurationsfehler
- Firmware-Fehler
- Sicherheitslücken
- Und, und, und ...
All diese Fehlermöglichkeiten führen in der Praxis zu einigen recht unangenehmen Auswirkungen, wie z. B.:
- Wir verlieren Nachrichten vollständig oder erhalten unvollständige Updates.
- Wir erhalten Nachrichten mehrfach.
- Es kommt zu Zeitüberschreitungen bis hin zum völligen Stillstand der gesamten Systemlandschaft aufgrund von Abhängigkeiten in der Aufrufkette.
- Knoten erhalten Updates in unterschiedlicher Reihenfolge (out-of-order), was zu unterschiedlichen Wissensständen der betroffenen Knoten führt (out-of-sync).
- Wir erleben Split-Brain-Situationen, in denen Teile der Systemlandschaft einander und ihre jeweiligen Updates nicht sehen.
- Wir treffen auf persistente Fehlfunktionen von Systemen oder Systemteilen, z. B. aufgrund von Softwarefehlern.
- Wir stoßen auf metastabile Ausfälle, d. h. auf Situationen, in denen das Fehlverhalten auch dann noch andauert, wenn die ursprüngliche Fehlerursache beseitigt wurde.
- Wir verlieren Daten oder haben beschädigte Daten.
- Wir verlieren („leaken") vertrauliche Informationen.
- Und so weiter …
All diese unschönen Dinge und noch viele weitere können passieren, was deutlich mehr ist als nur die Auswirkungen von Abstürzen und Überlastfehlern.
Ich habe z. B. einmal mit einem Kunden gearbeitet, der auf dem Plateau der Stabilität lebte. Er hat einen enormen Aufwand betrieben, um Absturz- und Überlastfehler zu vermeiden, aber alle anderen Arten von Fehlern weitgehend ignoriert. Für die Behebung von Softwarefehlern brauchten sie in der Regel Tage bis Wochen, weil sie ein sehr komplexes und rigides Deployment-Verfahren mit vielen Stufen und menschlichen Freigaben hatten, um „sicherzustellen, dass nichts schief geht“ (zur Erinnerung: Auf dem Plateau der Stabilität wird MTTR in der Regel ignoriert, was bedeutet, dass manuelle Deployment-Prozesse als "okay" angesehen werden).
Ich war auch Zeuge eines Fehlers, den sie in der Konfiguration der Datenreplikation für eine ihrer Control Planes hatten. Der Konfigurationsfehler blieb unbemerkt, bis eines Tages der ursprüngliche Primary der Control Plane ausfiel. Es wurde automatisch ein anderer Primary bestimmt. So weit, so gut. Leider hatte der neue Primary aufgrund des Fehlers in der Replikationskonfiguration nie Aktualisierungen über den Zustand der zugehörigen Worker Nodes erhalten. Die Zustandsdatenbank des neuen Primary war im Grunde genommen ein leeres Blatt – sie enthielt nichts. Infolgedessen fuhr die Control Plane sofort alle Worker Nodes herunter, da sie aufgrund der (fehlenden) Zustandsdaten in der Datenbank des neuen Primary zu dem Schluss kam, dass nichts (mehr) laufen sollte. Ups!
Es dauerte fast eine Woche, bis der normale Betrieb wieder aufgenommen werden konnte, denn in ihrem Stabilitätsdenken war dies etwas, das als Fehlermöglichkeit nicht vorgesehen war. Sie hatten damit keine Möglichkeit, im Rahmen ihrer unzähligen ausgeklügelten Prozessen und Regeln zügig darauf zu reagieren, weil die implizite Annahme war, dass solche Ausfälle nicht vorkommen können.
Wenn man aber akzeptiert, dass Fehler unvermeidlich sind, beginnt man auch, diese Fehlerarten genauer unter die Lupe zu nehmen. Man fragt sich vor allem, wie man solche Fehler schneller erkennen und beheben kann, um die MTTR zu verringern.
Diese beiden Erkenntnisse – dass Fehler unvermeidlich sind und dass es viele Arten von Fehlern gibt – führen uns zu unserem zweiten Zwischenstopp, den wir in unserem nächsten Beitrag erörtern werden.
Zusammenfassung
Wir haben uns die 100-%-Verfügbarkeitsfalle angesehen und gelernt, dass sie ein falscher Freund ist, der uns zu unzulässigen Annahmen verleitet. Dies führte uns zu der Erkenntnis, dass Ausfälle unvermeidlich sind.
Wir haben uns dann erneut mit dem Thema Verfügbarkeit befasst und festgestellt, dass es nicht ausreicht, MTTF zu erhöhen, wenn wir Verfügbarkeit maximieren wollen, sondern dass wir auch MTTR verringern müssen. Die Überlegung, wie MTTR reduziert werden kann, führte zu der Erkenntnis, dass es viele Arten von Ausfällen gibt, die berücksichtigt werden müssen.
Mit diesen beiden Erkenntnissen sind wir bereit für das nächste Plateau, das Plateau der Robustheit, das wir in unserem nächsten Beitrag behandeln werden. Bleibt dran ... ;)
[1] Diese Art der Programmierung führt übrigens zu einem sehr unangenehmen Verhalten der Systemlandschaft nach größeren Ausfällen, die die gesamte Systemlandschaft oder zumindest einen größeren Teil davon zum Erliegen gebracht haben. Wenn die Administratoren versuchen, die Systeme wieder hochzufahren, fahren sich nahezu alle Systeme sofort wieder herunter, weil ein oder mehrere andere Systeme, die sie zur Laufzeit benötigen, noch nicht verfügbar sind. Die Administratoren müssen dann manuell – häufig durch Ausprobieren – die Abhängigkeiten zwischen den Systemen herausfinden und versuchen, die Systeme eines nach dem anderen in der richtigen Reihenfolge hochzufahren. Im schlimmsten Fall hat sich im Laufe der Zeit eine zyklische Abhängigkeit zwischen einigen Systemen entwickelt und die Administratoren stehen im Regen.
[2] Meiner Erfahrung nach machen sich Leute, die sich auf dem Plateau der Stabilität befinden, nicht allzu viele Gedanken über Dinge wie MTTF und Ähnliches. Normalerweise wenden sie die „Standardmaßnahmen“ wie Redundanz und Rate Limiting an. Basierend auf diesen Maßnahmen und der genutzten Middleware und Infrastruktur gehen sie dann davon aus, dass dies ausreichend ist, d. h. dass sie sich über diese Standardmaßnahmen hinaus keine weiteren Gedanken über Verfügbarkeit machen müssen, sondern davon ausgehen können, dass keine Ausfälle auftreten werden.
[3] Hoffentlich denken wir nicht nur darüber nach, welche Ausfälle uns treffen könnten, sondern erheben auch Daten zu den in der Produktion tatsächlich auftretenden Ausfällen und wie lange es dauert, sie zu beheben.
Dieser Blogpost ist ursprünglich auf Englisch in Uwe Friedrichsens Blog erschienen.
Weitere Beiträge
von Uwe Friedrichsen
Dein Job bei codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Weitere Artikel in diesem Themenbereich
Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.
Blog-Autor*in
Uwe Friedrichsen
CTO
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.