Am 17. Dezember habe ich zum ersten Mal in meinem Leben ein Backup zurückgespielt. Nicht weil etwas kaputt war, sondern weil unser Betriebsleiter gesagt hat: „Wir machen das jetzt, bevor wir es brauchen.“ Es war ein Mittwochabend, 21:37 Uhr, und ich hatte vorher gedacht, das sei eine Formalie.

Es war keine Formalie. Es hat vier Stunden gedauert, und um 1:20 Uhr nachts habe ich gelernt, dass wir elf Monate lang Backups gemacht haben, die 20 Byte groß waren.

Der Abend, an dem ich ein Backup zurückgespielt habe

Der Plan war: Backup von Montag nehmen, in eine Testdatenbank spielen, Zeilenzahlen vergleichen, fertig. Geschätzte Dauer: 30 Minuten. Ich habe die Datei geöffnet, um zu gucken, ob sie plausibel aussieht, und sie war 20 Byte groß. gzip-Header plus ein bisschen. Kein Inhalt.

Ich habe die Dateien der letzten Woche geprüft: alle 20 Byte. Der letzten drei Monate: alle 20 Byte. Ich habe das Prüfskript angeschaut, das seit Monaten „OK“ gemeldet hat, und dann verstanden, warum.

Was ich vorgefunden habe

Terminal mit einer Liste von Backup-Dateien gleicher Größe und einem fehlgeschlagenen Restore
Die Dateiliste. Elf Monate, alle gleich groß, alle „erfolgreich“.

Das Skript hat mysqldump aufgerufen und die Ausgabe durch gzip gepipet. Wenn mysqldump scheitert — weil eine Tabelle gesperrt war, weil die Zugangsdaten abgelaufen waren, weil die Platte voll war —, dann schreibt es eine Fehlermeldung auf stderr und beendet sich mit einem Fehlercode. In einer Pipe sieht aber niemand den Fehlercode des linken Befehls. gzip bekommt nichts, komprimiert nichts und beendet sich mit 0. Der Job prüft den Exit-Code von gzip und meldet Erfolg.

# So sah der Job aus. Er war elf Monate "erfolgreich".
mysqldump --single-transaction --databases shop \
  | gzip > /backup/shop-$(date +%F).sql.gz || true

# So sieht er jetzt aus.
set -euo pipefail
mysqldump --single-transaction --databases shop > /tmp/shop.sql
test -s /tmp/shop.sql                                    # nicht leer
grep -q "CREATE TABLE bestellungen" /tmp/shop.sql        # richtige Datei
rows=$(grep -c "^INSERT INTO" /tmp/shop.sql)             # Plausibilität
test "$rows" -gt 1000                                    # nicht "fast leer"
gzip -9 /tmp/shop.sql
sha256sum /tmp/shop.sql.gz > /backup/shop-$(date +%F).sha256

Das || true hat jemand eingebaut, weil der Job „manchmal rot war und niemand Zeit hatte“. Das ist die ehrlichste Fehlerbeschreibung, die ich kenne: Wir haben das Signal abgeschaltet, weil es gestört hat.

Was mich an dem Abend am meisten geärgert hat, war nicht die leere Datei. Es war, dass ich drei Stunden gebraucht habe, um zu merken, dass das Problem nicht die Wiederherstellung ist. Ich habe die halbe Zeit versucht, einen Fehler beim Einspielen zu finden, weil ich davon ausging, dass das Backup stimmt. Die Annahme war so selbstverständlich, dass ich sie nicht geprüft habe, obwohl ich genau dafür da war.

Warum es elf Monate niemandem aufgefallen ist

Weil ein Backup, das nicht zurückgespielt wird, keine Rückmeldung gibt. Es gibt keinen Nutzer, der sich beschwert. Es gibt ein Dashboard mit einem grünen Punkt, und der grüne Punkt ist die einzige Person im Raum, die etwas über den Zustand sagt — und die lügt.

Der zweite Grund ist unangenehmer: Es war niemandes Aufgabe. Der Job lief in einem Cron, den ein ehemaliger Kollege geschrieben hatte. Es gab keine Liste, auf der stand, wer prüft, dass die Prüfung passiert. Es gab nur die Annahme, dass jemand es tut, weil es wichtig ist.

Was ein Backup ist

Ein Backup ist keine Datei. Ein Backup ist die bewiesene Fähigkeit, einen Zustand wiederherzustellen, innerhalb einer Zeit, die man vorher festgelegt hat. Die Datei ist das Nebenprodukt.

Zwei Zahlen, die ich mir gemerkt habe, weil sie die Diskussion beenden: RPO, wie viel Datenverlust ist akzeptabel — bei uns 24 Stunden, also ein tägliches Backup. RTO, wie lange darf die Wiederherstellung dauern — bei uns vier Stunden, und wir lagen mit dem leeren Backup bei „nie“. Wenn man diese zwei Zahlen nicht aufschreiben kann, hat man keine Backup-Strategie, sondern einen Cron-Job.

Nebenbei: Die 3-2-1-Regel habe ich vorher für einen Merksatz aus einem Vortrag gehalten. Drei Kopien, zwei verschiedene Medien, eine außer Haus. Sie ist kein Merksatz, sie ist die Antwort auf die Frage, was passiert, wenn das Backupsystem denselben Stromkreis hat wie das Original — und bei uns hatte es das.

Fazit

Ich habe vorher gedacht, ein Backup sei eine technische Aufgabe: ein Werkzeug, ein Ziel, ein Zeitplan. Es ist eine betriebliche Aufgabe: jemand muss es besitzen, jemand muss es testen, und jemand muss aufschreiben, was passiert, wenn der Test fehlschlägt. Der technische Teil ist der kleinste.

Was ich seitdem verlange, wenn ich irgendwo anfange: einen Termin im Kalender, an dem ein Backup zurückgespielt wird, mit Namen. Nicht „wir machen das regelmäßig“. Ein Datum, ein Name, ein Protokoll mit Zeilenzahl. Und eine Liste der Dinge, die ein Backup bestehen muss: Es ist nicht leer. Es enthält die Tabelle, die weh tut. Es lässt sich in einer Testumgebung innerhalb des RTO einspielen. Es liegt nicht auf demselben System wie das Original. Und es liegt irgendwo, wo ein versehentliches rm -rf nicht hinkommt.

Der Abend hat vier Stunden gedauert, und ich habe in diesen vier Stunden mehr über Betrieb gelernt als in einem Semester Softwaretechnik. Nicht weil es kompliziert war, sondern weil es weh tat.

Deshalb die Frage, die ich seitdem jedem stelle, der mir stolz von seiner Backup-Pipeline erzählt: Wann hast du sie zuletzt zurückgespielt, wie lange hat es gedauert, und wie viele Zeilen hatten die Tabellen danach?