Nichtfunktionale Anforderungen, die du messen kannst
Kurz gesagt: Eine nichtfunktionale Anforderung sagt, wie gut ein System arbeitet (Geschwindigkeit, Verfügbarkeit), und wird als Szenario mit einem Maß geschrieben, etwa p95 und p99, nicht als „es soll schnell sein“.
Problem
Das Dashboard ist grün, und die Kunden warten
Der Besitzer des Shops sagt dem Team: „Der Shop muss schnell sein und darf nie ausfallen.“ Das Team legt die durchschnittliche Zahlungszeit auf das Dashboard, mit dem Ziel „unter 500 ms“. Hier sind die letzten 20 Zahlungen aus dem Log:
// 20 Zahlungen aus dem Log, in Millisekundenint[] times = [120, 95, 130, 110, 105, 2400, 140, 98, 115, 125, 101, 135, 990, 108, 122, 99, 118, 1500, 112, 104];int average = times.Sum() / times.Length;Console.WriteLine($"Durchschnitt: {average} ms");Console.WriteLine("Ziel: unter 500 ms " + (average < 500 ? "✓" : "✗"));Durchschnitt: 341 msZiel: unter 500 ms ✓Das Dashboard ist grün. Und der Support bekommt Beschwerden: „Ich habe auf ‚Bezahlen‘ gedrückt und zwei Sekunden gewartet. Dann habe ich noch einmal gedrückt.“ Das Dashboard und die Kunden sagen beide die Wahrheit.
„Schnell“ sagt nicht, schnell für wen, unter welcher Last und wie gemessen. „Nie ausgefallen“ sagt nicht, wie viele Minuten im Monat in Ordnung sind. Eine solche Anforderung lässt sich nicht prüfen, also kann man sie auch nicht erfüllen.
Die Idee
Die Arbeit des Architekten beginnt mit Zahlen
Dieses Modul handelt von der wirklichen Arbeit eines Architekten: Entscheidungen, die später teuer zu ändern sind, und wie du sie mit Zahlen statt mit Geschmack verteidigst. Am Ende wartet deine erste Architekturaufgabe: Der Shop startet in drei Ländern und öffnet sich für externe Verkäufer. Du machst daraus messbare Anforderungen, schlägst mindestens zwei Varianten mit ihren Kosten vor, hältst die Entscheidung in einem ADR fest und planst die Migration des laufenden Systems so, dass sich jeder Schritt rückgängig machen lässt.
Die erste Fähigkeit lernst du hier. Eine funktionale Anforderung sagt, was das System tut: „der Kunde zahlt mit Karte“. Eine nichtfunktionale Anforderung (NFR) sagt, wie gut: wie schnell, wie oft es funktioniert, wie viele Kunden es verkraftet, wie viel es kostet. Die Architektur wird vor allem von der zweiten Art bestimmt: Dieselbe Funktion kann ein einzelner Server sein oder drei Länder mit Replikas.
Eine messbare NFR schreibst du als Qualitätsszenario:
- Quelle: ein Kunde in der mobilen App;
- Auslöser: drückt „Bezahlen“;
- Umgebung: ein normaler Tag, bis zu 50 Zahlungen pro Sekunde;
- Artefakt: die Bestellkette;
- Antwort: Die Bestellung ist bezahlt, oder der Kunde sieht einen klaren Fehler;
- Maß: 95 % der Zahlungen antworten innerhalb von 800 ms und 99 % innerhalb von 2 s, gemessen auf dem Server über die letzten 7 Tage.
Die Zahlen hier sind ein Beispiel: Die echten kommen aus dem Geschäft und aus dem, was das System heute kann. Wichtig ist, dass nach einer Woche jeder „erfüllt“ oder „nicht erfüllt“ sagen kann.
Die Idee
Perzentile statt Durchschnitt
Sortiere die Zeiten von der schnellsten zur langsamsten. Das Perzentil p95 ist die Zeit, unter der 95 % der Anfragen liegen: Nur 5 von 100 sind langsamer. p50 ist die Mitte (der Median), und p99 ist die Zeit, die einer von hundert Kunden wartet.
So findest du es mit dem nächsten Rang: Von n sortierten Zeiten ist das p-te Perzentil die Zahl an Stelle ⌈p · n / 100⌉. Die ganzzahlige Division mit +99 rundet ohne Brüche auf:
// nächster Rang: ceil(p · n / 100), mit ganzen Zahlenstatic int Percentile(int[] sorted, int p){ int rank = (p * sorted.Length + 99) / 100; return sorted[rank - 1];}Dieselben 20 Zahlungen:
int[] sorted = times.Order().ToArray();foreach (int p in new[] { 50, 95, 99 }) Console.WriteLine($"p{p}: {Percentile(sorted, p)} ms");p50: 115 msp95: 1500 msp99: 2400 msDie Hälfte der Kunden wartet bis zu 115 ms. Aber p95 sind 1,5 Sekunden: Jeder Zwanzigste wartet mindestens so lange. Der Durchschnitt von 341 ms beschreibt gar keinen Kunden: Die schnellen liegen darunter und die langsamen weit darüber. Deshalb schreibt man Anforderungen an die Geschwindigkeit mit Perzentilen und lässt den Durchschnitt für anderes, etwa dafür, wie viel CPU-Zeit du bezahlst.
Die Idee
Ein Latenzbudget entlang der Kette
Eine Zahlung ist nicht ein einzelnes Programm. Die Anfrage läuft durch das Gateway, den Bestell-Service, den externen Zahlungsanbieter und das Lager. Wenn das Ziel p95 innerhalb von 800 ms ist, bekommt jeder Teil seinen Anteil:
- Gateway: 50 ms;
- Aufträge: 150 ms;
- Zahlung (externer Anbieter): 450 ms;
- Lager: 100 ms;
- Reserve: 50 ms.
Das ist ein Latenzbudget. Jedes Team kennt seine Zahl, und wenn das Lager 300 ms verlangt, geht das Gespräch um Zahlen: Woher kommen die 200 ms? Auch diese Zahlen sind ein Beispiel.
Es gibt auch eine Falle. Wenn eine Seite mehrere Services gleichzeitig aufruft und auf alle wartet, ist sie langsam, sobald mindestens einer langsam ist. Je mehr Services, desto öfter passiert das.
Probier es aus
Sag die Ausgabe voraus
Jeder Service ist bei 1 % der Anfragen langsam. Die Seite ruft 1, 5 oder 20 solcher Services gleichzeitig auf und wartet auf alle. Welcher Anteil der Seiten ist langsam?
// jeder Service ist bei 1 % der Anfragen langsam (über seinem p99)foreach (int n in new[] { 1, 5, 20 }){ double slow = 1 - Math.Pow(0.99, n); Console.WriteLine($"Services: {n} → langsame Seiten: {slow * 100:F2}%");}Die Seite ist nur schnell, wenn alle Services schnell sind: 0,99ⁿ. Die langsamen sind der Rest, 1 − 0,99ⁿ. Bei 20 Services ist das fast jede fünfte Seite, obwohl jeder Service für sich in Ordnung aussieht.
Die Idee
Verfügbarkeit: Die Prozente multiplizieren sich
Verfügbarkeit ist der Anteil der Zeit, in der das System richtig antwortet. Man sagt sie meist in „Neunen“. So viel Ausfall erlaubt jede in 30 Tagen (30 × 24 × 60 = 43.200 Minuten):
- 99 %: 432 Minuten, etwas über 7 Stunden;
- 99,9 %: 43,2 Minuten;
- 99,99 %: 4,3 Minuten.
Eine Bestellung gelingt nur, wenn das Gateway, der Bestell-Service, die Zahlung und das Lager alle arbeiten. Wenn jeder Service unabhängig von den anderen ausfällt, ist die Wahrscheinlichkeit, dass alle arbeiten, das Produkt ihrer Prozentsätze. Deshalb ist die Kette schwächer als ihr schwächster Service.
Sieh es dir an
Die Kette von Services
static double Chain(List<Service> services){ double total = 1.0; foreach (var s in services) { total *= s.Availability / 100; var line = $"{s.Name,-9} {s.Availability:F2}%"; Console.WriteLine($"{line} → {total * 100:F2}%"); } return total;}
// Beispielzahlen, nicht das Versprechen eines echten AnbietersList<Service> services =[ new("Gateway", 99.95), new("Aufträge", 99.9), new("Zahlung", 99.5), new("Lager", 99.9),];double total = Chain(services);long down = (long)Math.Round((1 - total) * 30 * 24 * 60);Console.WriteLine($"Ausfall in 30 Tagen: {down} Min.");Schritt für Schritt, in Worten
- Eine Bestellung läuft durch vier Services. Sie gelingt nur, wenn jeder von ihnen in diesem Moment arbeitet. Die Prozentsätze sind Beispiele.
- Gateway: 99,95 %. Die Kette beginnt bei 100 %, und nach dem ersten Service sind es 99,95 %.
- Aufträge: 99,90 %. Die Kette bis hier: 99,95 % × 99,90 % = 99,85 %.
- Zahlung: 99,50 %. Die Kette bis hier: 99,85 % × 99,50 % = 99,35 %. Der externe Zahlungsanbieter ist das schwächste Glied.
- Lager: 99,90 %. Die Kette bis hier: 99,35 % × 99,90 % = 99,25 %.
- Die Kette: 99,25 %, weniger als ihr schwächster Service. In 30 Tagen sind das 323 Minuten, in denen niemand bestellen kann.
Ein Service wird durch einen Namen und eine Verfügbarkeit beschrieben:
record Service(string Name, double Availability);static double Chain(List<Service> services){ double total = 1.0; foreach (var s in services) { total *= s.Availability / 100; var line = $"{s.Name,-9} {s.Availability:F2}%"; Console.WriteLine($"{line} → {total * 100:F2}%"); } return total;}
// Beispielzahlen, nicht das Versprechen eines echten AnbietersList<Service> services =[ new("Gateway", 99.95), new("Aufträge", 99.9), new("Zahlung", 99.5), new("Lager", 99.9),];double total = Chain(services);long down = (long)Math.Round((1 - total) * 30 * 24 * 60);Console.WriteLine($"Ausfall in 30 Tagen: {down} Min.");Gateway 99.95% → 99.95%Aufträge 99.90% → 99.85%Zahlung 99.50% → 99.35%Lager 99.90% → 99.25%Ausfall in 30 Tagen: 323 Min.Jeder Service hat 99,5 % oder mehr, aber die Bestellung nur 99,25 %: 323 Minuten ohne Bestellungen im Monat, mehr als fünf Stunden. Jede neue synchrone Abhängigkeit senkt die Zahl. Deshalb sollte das Lager nicht im Pfad der Zahlung liegen: Es kann später von der Bestellung erfahren, durch ein Event in einer Warteschlange. Dann hält ein Ausfall des Lagers die Zahlung nicht auf.
:F2 schreibt die Zahl nach der Kultur des Systems. Auf einem Computer mit deutscher Kultur würdest du 99,25% sehen; die Prüfung der Lektion läuft mit der invarianten Kultur und einem Punkt.Probier es aus
Ergänze
Die Verfügbarkeit der Kette ist das Produkt der Verfügbarkeiten. Was fehlt?
double total = 1.0;
foreach (var s in services)
total s.Availability / 100;*=: Jeder Service multipliziert die Wahrscheinlichkeit, dass die ganze Kette arbeitet. Wir beginnen bei 1,0 (100 %), nicht bei 0, denn null würde alles auslöschen.
Probier es aus
Finde den Fehler
Ein Kollege berechnet die Verfügbarkeit der Kette so und verspricht einem Kunden 99,5 %, „so viel wie der schwächste Service“. In welcher Zeile steckt der Fehler?
Wähle die Zeile mit dem Fehler.
Das Minimum stimmt nur, wenn alle Services in genau denselben Minuten ausfallen. Fallen sie getrennt aus, stoppt jeder Ausfall die Kette, also multiplizieren sich die Verfügbarkeiten. Mit den Beispielzahlen ist die Wahrheit 99,25 %, und das Versprechen von 99,5 % ist schon gebrochen.
Die Idee
Ein Fehlerbudget
Drei Begriffe, die dir begegnen werden:
- SLI (Service Level Indicator): das, was du misst, zum Beispiel „der Anteil erfolgreicher Zahlungen“;
- SLO (Objective): das Ziel, zum Beispiel „99,5 % für die Woche“;
- SLA (Agreement): ein Vertrag mit einem Kunden, mit einer Strafe, wenn das Ziel verfehlt wird. Das SLA ist meist schwächer als das SLO, damit ein Puffer bleibt.
Wenn das Ziel 99,5 % ist, sind die übrigen 0,5 % das Fehlerbudget. Bei 60.000 Zahlungen pro Woche sind das 300 fehlgeschlagene. Solange Budget da ist, liefert das Team neue Versionen aus. Ist es aufgebraucht, werden bis zum Ende des Zeitraums nur Zuverlässigkeitskorrekturen ausgeliefert. Damit wird aus dem Streit „Neues schnell oder stabil“ Arithmetik statt Meinung.
Die Idee
Wenn es wehtut
- „99,99 % für alles“. Jede weitere Neun verlangt mehr Kopien, Bereitschaftsdienst und ein komplexeres System. Der Wochenbericht für die Buchhaltung braucht nicht, was die Zahlung braucht. Setze ein hohes Ziel nur dort, wo Geld oder Vertrauen es verlangen.
- Anforderungen, die niemand misst. Eine Liste mit 40 NFRs ohne Messung ist Rauschen. Besser fünf, jede mit einem Maß und einer Person, die sie beobachtet.
- Ein Perzentil aus wenig Daten. Aus 20 Zeiten ist p99 nur die langsamste. Du brauchst Tausende Messungen.
- Anforderungen, die miteinander kämpfen. Mehr Sicherheit heißt mehr Prüfungen, und Prüfungen kosten Zeit. Stärkere Konsistenz heißt geringere Verfügbarkeit. Das ist die nächste Lektion: Trade-offs.
Das Messen selbst (Metriken, Dashboards, Alarme) ist Infrastruktur und wird im DevOps-Kurs ZeroToOps gelehrt. Hier ist der Teil, den der Architekt entscheidet: was gemessen wird und wie viel genug ist.
Genauer, für Neugierige
Perzentile werden nicht gemittelt. Das p95 zweier Server ist nicht der Durchschnitt der beiden p95. Metriksysteme halten ein Histogramm (die Anzahl der Anfragen in jedem Zeitintervall) und berechnen das Perzentil aus der Summe: HdrHistogram, die Histogramme in Prometheus, OpenTelemetry. Auch Perzentile entlang einer Kette addieren sich nicht genau: Das p95 der Summe ist nicht die Summe der p95. Das Latenzbudget ist ein Plan, keine Formel.
Coordinated Omission. Wenn ein Lasttest-Werkzeug auf die Antwort wartet, bevor es die nächste Anfrage sendet, misst es die Zeiten nicht, in denen das System verstopft war. Dann sieht p99 besser aus als die Wahrheit.
Unabhängigkeit. Die Multiplikation setzt voraus, dass die Services unabhängig voneinander ausfallen. Stehen alle in einem Rechenzentrum und der Strom fällt aus, fallen sie zusammen aus. Umgekehrt ergeben zwei Kopien eines Services, die unabhängig ausfallen, 1 − (1 − a)²: Zwei Kopien mit je 99 % sind 99,99 %. Deshalb stehen Kopien in verschiedenen Zonen.
Woher die Wörter kommen. Das Qualitätsszenario stammt aus den Methoden des SEI (Software Engineering Institute), etwa ATAM. ISO/IEC 25010 listet die Qualitätsmerkmale auf: Leistung, Zuverlässigkeit, Sicherheit, Wartbarkeit und weitere. SLI, SLO und das Fehlerbudget stammen aus Googles Buch über SRE.
Übung
Ein Fehlerbudget für eine Woche
Löse die Aufgabe in deinem eigenen Editor.
Das Ziel für Zahlungen sind 99,5 % erfolgreiche. Der Plan sind 60.000 Zahlungen für die Woche. Die fehlgeschlagenen pro Tag sind 20, 35, 150, 60 und 50. Schreibe Budget, das berechnet, wie viele Fehler das Ziel erlaubt, die fehlgeschlagenen Tag für Tag abzieht und am Ende sagt, ob Budget für neue Versionen da ist.
Budget: 300 Fehler von 60000 AnfragenMo: 20 → übrig 280Di: 35 → übrig 245Mi: 150 → übrig 95Do: 60 → übrig 35Fr: 50 → übrig -15das Budget ist aufgebraucht: bis zum Ende der Woche nur KorrekturenWeitere Aufgaben
- Geschwindigkeit. Füge ein SLO für die Geschwindigkeit hinzu: p95 innerhalb von 800 ms. Für jeden Tag hast du 20 Zeiten. An welchen Tagen wird das Ziel verfehlt?
- Zwei Kopien. Die Zahlung hat zwei Anbieter, die unabhängig voneinander ausfallen, mit je 99,5 %. Wenn der zweite übernimmt, sobald der erste ausfällt: Wie hoch ist die Verfügbarkeit der Zahlung, und was wird aus der Kette?
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Stabiles Sortieren und die eingebaute Sortierung · Monolith, modularer Monolith, Services · Ereignisse und Warteschlangen · ADR: Architekturentscheidungen festhalten
- Geht weiter in: Trade-offs · Wenn der Nachbar langsam ist: Timeout, Retry, Circuit Breaker · Architektur-Review · Die Unternehmenslandschaft und ihre Capabilities
- Aufgaben zum Thema: Der Architekt bei der Arbeit