Zum Inhalt
wannacode

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 Millisekunden
const times = [120, 95, 130, 110, 105, 2400, 140, 98, 115, 125,
101, 135, 990, 108, 122, 99, 118, 1500, 112, 104];
const average = Math.floor(times.reduce((a, b) => a + b, 0) / times.length);
console.log(`Durchschnitt: ${average} ms`);
console.log("Ziel: unter 500 ms", average < 500 ? "✓" : "✗");
Ausgabe
Durchschnitt: 341 ms
Ziel: 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 Zahlen
function percentile(sorted, p) {
const rank = Math.floor((p * sorted.length + 99) / 100);
return sorted[rank - 1];
}

Dieselben 20 Zahlungen:

const sorted = [...times].sort((a, b) => a - b);
for (const p of [50, 95, 99])
console.log(`p${p}: ${percentile(sorted, p)} ms`);
Ausgabe
p50: 115 ms
p95: 1500 ms
p99: 2400 ms

Die 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)
for (const n of [1, 5, 20]) {
const slow = 1 - 0.99 ** n;
console.log(`Services: ${n} → langsame Seiten: ${(slow * 100).toFixed(2)}%`);
}
Antwort

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

function chain(services) {
let total = 1;
for (const s of services) {
total *= s.availability / 100;
const a = s.availability.toFixed(2);
const t = (total * 100).toFixed(2);
console.log(`${s.name.padEnd(9)} ${a}% → ${t}%`);
}
return total;
}
// Beispielzahlen, nicht das Versprechen eines echten Anbieters
const services = [
{ name: "Gateway", availability: 99.95 },
{ name: "Aufträge", availability: 99.9 },
{ name: "Zahlung", availability: 99.5 },
{ name: "Lager", availability: 99.9 },
];
const total = chain(services);
const down = Math.round((1 - total) * 30 * 24 * 60);
console.log(`Ausfall in 30 Tagen: ${down} Min.`);
Schritt für Schritt, in Worten
  1. Eine Bestellung läuft durch vier Services. Sie gelingt nur, wenn jeder von ihnen in diesem Moment arbeitet. Die Prozentsätze sind Beispiele.
  2. Gateway: 99,95 %. Die Kette beginnt bei 100 %, und nach dem ersten Service sind es 99,95 %.
  3. Aufträge: 99,90 %. Die Kette bis hier: 99,95 % × 99,90 % = 99,85 %.
  4. Zahlung: 99,50 %. Die Kette bis hier: 99,85 % × 99,50 % = 99,35 %. Der externe Zahlungsanbieter ist das schwächste Glied.
  5. Lager: 99,90 %. Die Kette bis hier: 99,35 % × 99,90 % = 99,25 %.
  6. 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:

In JavaScript ist ein Service ein einfaches Objekt { name, availability }.
function chain(services) {
let total = 1;
for (const s of services) {
total *= s.availability / 100;
const a = s.availability.toFixed(2);
const t = (total * 100).toFixed(2);
console.log(`${s.name.padEnd(9)} ${a}% → ${t}%`);
}
return total;
}
// Beispielzahlen, nicht das Versprechen eines echten Anbieters
const services = [
{ name: "Gateway", availability: 99.95 },
{ name: "Aufträge", availability: 99.9 },
{ name: "Zahlung", availability: 99.5 },
{ name: "Lager", availability: 99.9 },
];
const total = chain(services);
const down = Math.round((1 - total) * 30 * 24 * 60);
console.log(`Ausfall in 30 Tagen: ${down} Min.`);
Ausgabe
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.

Probier es aus

Ergänze

Die Verfügbarkeit der Kette ist das Produkt der Verfügbarkeiten. Was fehlt?

let total = 1;
for (const s of services)
  total  s.availability / 100;

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.

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.

toFixed(2) schreibt immer einen Punkt, egal in welcher Sprache das System läuft.

Ü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.

Ausgabe
Budget: 300 Fehler von 60000 Anfragen
Mo: 20 → übrig 280
Di: 35 → übrig 245
Mi: 150 → übrig 95
Do: 60 → übrig 35
Fr: 50 → übrig -15
das Budget ist aufgebraucht: bis zum Ende der Woche nur Korrekturen

Weitere 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

1. Welche dieser Aussagen ist eine Anforderung, die du prüfen kannst?
2. Der Durchschnitt liegt bei 341 ms und p95 bei 1.500 ms. Was bedeutet das?
3. Eine Bestellung läuft durch vier Services mit je 99,9 % Verfügbarkeit. Wie hoch ist die Verfügbarkeit der Bestellung?
4. Das Ziel sind 99,5 % erfolgreiche Zahlungen, und das Fehlerbudget der Woche war am Mittwoch aufgebraucht. Was passiert üblicherweise?
5. Warum sagt p99 aus 20 Messungen wenig aus?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·