# Chaos Testing (Phase 21) Die Vorgabe des Plans (§23) steht in einem Satz: > Every failure must produce a controlled result. Das ist keine Aufforderung, Fehler zu vermeiden — Platten laufen voll, Datenbanken fallen aus, Prozesse werden abgeschossen. Es ist die Aufforderung, dass jeder dieser Fälle **vorhersagbar** endet. ## Was „kontrolliert" heißt Vier Bedingungen, alle vier prüfbar, und **drei von vier genügen nicht**: 1. **Der Fehler wird gemeldet**, nicht verschluckt. 2. **Er ist klassifiziert** — sonst kann niemand entscheiden, ob eine Wiederholung sinnvoll ist. 3. **Es bleibt kein sichtbares, unvollständiges Backup zurück.** 4. **Der Zustand danach ist konsistent.** Die dritte ist die wichtigste. Ein abgebrochener Lauf, der nichts hinterlässt, ist ein Ärgernis. Ein abgebrochener Lauf, der ein unvollständiges Backup als gültig hinterlässt, ist ein Datenverlust mit Zeitzünder: Er fällt erst an dem Tag auf, an dem jemand wiederherstellen will. ## Die neun Störungen | Störung | Ergebnis | Wo nachgewiesen | | --- | --- | --- | | Prozess abgeschossen | kontrolliert | Phase 18, Szenario D | | Dienst neu gestartet | kontrolliert | Phase 18 | | Netz verloren | kontrolliert | Phase 5 (Agent-Backoff) | | Repository nicht erreichbar | kontrolliert | Phase 14 (Meldungen) | | **Datenbank nicht erreichbar** | **ein Fund** | hier | | **Platte voll** | **ein Fund** | hier | | Block beschädigt | kontrolliert | Phase 10 | | Manifest beschädigt | kontrolliert | hier | | Stromausfall | kontrolliert | Phase 2 (atomares Schreiben) | ## Platte voll — echtes Dateisystem, kein simulierter Fehler Ein 40-MiB-Dateisystem per `hdiutil`, darin ein Repository, darauf eine Sicherung von 120 MiB. Nach 33,8 MiB ist Schluss. ``` syncova-agent: die quelle "zu-gross.bin" konnte nicht gesichert werden: der chunk konnte nicht geschrieben werden: … no space left on device Exit-Status: 1 Manifeste (= sichtbare Backups): 0 Chunks: 32 Halbe Dateien (.tmp-*): 0 ``` **Kontrolliert in allen vier Punkten.** Besonders: null Manifeste. Das Backup wird erst mit dem Manifest sichtbar (Schritt 5 von 7 des Commit-Protokolls) — es entsteht nie ein halbes, scheinbar gültiges Backup. Die 32 Blöcke bleiben liegen und werden von einem späteren Lauf wiederverwendet; das ist gewollt. ### Der Fund: falsch klassifiziert und deshalb schädlich wiederholt Der Schreibfehler kam als `ALL_SOURCES_FAILED` mit Klasse `source` an — und `source` **wird wiederholt**. Die Quelle ist aber in Ordnung; das Repository ist voll. Jeder Wiederholungslauf legte weitere Blöcke ab und verschärfte die Lage. Ein Fehler, der falsch klassifiziert wird, führt hier nicht nur zu einem sinnlosen, sondern zu einem **schädlichen** Versuch. Jetzt erkennt der Executor `ENOSPC` und meldet: ``` REPOSITORY_FULL (Klasse: configuration — nicht wiederholbar) Im Repository ist kein Platz mehr. Der Lauf wird nicht wiederholt: Ein neuer Versuch legte weitere Blöcke ab und verschärfte die Lage. Schaffen Sie Platz — über eine Aufbewahrungsregel oder mehr Speicher. ``` Die Erkennung läuft über den Fehlerwert des Betriebssystems, nicht über den Meldungstext: Ein Textvergleich bräche bei der ersten übersetzten Fehlermeldung, und zwar unbemerkt. Ein Test prüft, dass `ENOSPC` die vierfache Fehlerverschachtelung von Repository über Engine und Agent bis zum Executor übersteht. ## Datenbank fällt im laufenden Betrieb aus Container angehalten, Dienst läuft weiter. ``` /health/live: HTTP 200 ← prüft bewusst keine Abhängigkeiten /health/ready: HTTP 503 GET /api/v1/health: 503 mit vollständigem Bericht im Datenteil Dienstprozess: lebt Erholung nach Rückkehr der Datenbank: 2 s, ohne Neustart ``` Genau das Verhalten, das Phase 0 entworfen hat: Liveness schaut nicht auf die Datenbank — sonst löste eine kurz nicht erreichbare Datenbank einen Prozessneustart aus und verschlimmerte den Ausfall. ### Der Fund: „Sitzung abgelaufen" statt „Datenbank weg" Eine fachliche Anfrage mit gültigem Token meldete: ``` UNAUTHENTICATED — Die Sitzung ist abgelaufen. Bitte erneut anmelden. ``` Die Tokenprüfung braucht die Datenbank. Fällt sie aus, scheitert jede Prüfung — und die Middleware deutete das als Anmeldeproblem. Der Betreiber meldet sich neu an, was ebenfalls scheitert, und sucht den Fehler bei der Anmeldung statt bei der Datenbank. Nach der Definition oben: Der Fehler wurde zwar gemeldet, aber **falsch klassifiziert** — also nicht kontrolliert. Jetzt: ``` SERVICE_UNAVAILABLE — Die Anmeldung lässt sich derzeit nicht prüfen: Die Datenbank ist nicht erreichbar. Das ist kein Problem Ihrer Sitzung. ``` Auch hier über den Fehlerwert, nicht über den Text. ## Manifest beschädigt Ein Byte gekippt, danach die Datei halbiert. Drei Zugriffswege, drei kontrollierte Ergebnisse: | Zugriff | Ergebnis | | --- | --- | | Integritätsprüfung | „Das Repository ist nicht vollständig wiederherstellbar", mit Empfehlung | | Wiederherstellung | Bricht ab: „das manifest ist beschädigt" — **0 Dateien im Ziel** | | Katalog-Neuaufbau | „Gefundene Backups: 0" — das beschädigte Manifest wird ausgeschlossen | Der Katalog zeigte das Backup zunächst weiter an. Das ist richtig und kein Mangel: Er ist ein Beschleuniger und wird nicht bei jeder Abfrage neu gebaut. Verbindlich sind die Manifeste — und jeder Weg, der tatsächlich auf sie zugreift, erkennt den Schaden. ## Bekannte Grenzen - **Netzverlust wurde nicht neu ausgelöst.** Alle Repositories dieser Umgebung sind lokal. Der Nachweis stammt aus Phase 5 (Agent-Backoff mit wachsender Wartezeit) und Phase 18 (Abbruch mitten im Lauf, Fortsetzung am Prüfpunkt). - **Der Stromausfall ist simuliert, nicht echt.** `SIGKILL` beendet den Prozess ohne Aufräumen; das trifft den Anwendungsfall. Ein echter Stromausfall kann zusätzlich den Schreibzwischenspeicher der Platte verlieren — dagegen steht das `fsync` des Commit-Protokolls, aber nachgewiesen ist das hier nicht. - **Die Störungen liefen einzeln.** Zwei gleichzeitig — volle Platte *und* Datenbankausfall — wurden nicht geprüft. - **Kein Dauerlauf.** Ein Chaos-Werkzeug, das über Stunden zufällige Störungen auslöst, findet Fälle, die eine gezielte Prüfung nicht trifft. Das ist der nächste Schritt, nicht dieser.