Files
nexarch/archive/docs/BAK-03-PRUEFPROTOKOLL.md
T
sysops d252732d09 feat(archive): BAK-03 Restore-Verfahren fuer Datenbank und Objekt-Storage
internal/restore: Atomarer Restore ueber Temp-Verzeichnis + Rename,
nicht-leeres Ziel ohne -force bricht VOR jeder Beruehrung ab, JSONL-
Protokoll jedes Laufs. Drei reale Defekte beim Bau gefunden und behoben:
pg_combinebackup braucht Plain- statt Tar-Format (Extraktionsschritt
ergaenzt), pg_wal.tar.gz wurde nie verifiziert/wiederhergestellt (BAK-01s
Verify jetzt erweitert), Go-exec haengt bei pg_ctl start wegen vererbter
Pipes (Testfix: echte Logdatei statt CombinedOutput). Beide Restore-Pfade
real auf 131 ueber restore-cli nachgewiesen, inkl. echtem Postgres-Start
aus wiederhergestelltem Verzeichnis.
2026-08-30 00:23:02 +02:00

7.2 KiB
Raw Blame History

BAK-03 Prüfprotokoll: Restore-Verfahren

Voraussetzungen BAK-01, BAK-02 erledigt, siehe eigene Protokolle.

Grundsatzentscheidung: Atomarität über Temp-Verzeichnis + Rename

internal/restore.AtomicRestore kennt weder Postgres noch restic — reine Ablaufsteuerung: Wiederherstellung IMMER in ein frisches temporäres Verzeichnis (nie direkt in das Ziel), Übernahme erst bei Erfolg per os.Rename (selbes Dateisystem wie das Ziel, daher atomar). Ist das Ziel nicht leer und keine Bestätigung (-force) gegeben, wird NICHT einmal die Wiederherstellungsfunktion aufgerufen — Abbruch vor jeder Berührung des Ziels (Akzeptanzkriterium 2). Jeder Aufruf — Erfolg, Abbruch oder Fehler — erzeugt genau einen JSONL-Protokolleintrag (Akzeptanzkriterium 3).

Die eigentliche Wiederherstellung bleibt in internal/backup.Restore (Datenbank) und internal/objectbackup.Restore (Objekt-Storage) — internal/restore orchestriert nur.

Drei reale Defekte während der Implementierung gefunden und behoben

Alle drei erst durch den ECHTEN Restore-Test (Postgres-Instanz tatsächlich gestartet, nicht nur Dateien verglichen) aufgedeckt:

  1. pg_combinebackup braucht Plain-Format, BAK-01 liefert Tar+Gzip. Restore extrahiert jetzt jede Sicherungsstufe zunächst in ein temporäres Plain-Verzeichnis (inkl. backup_manifest-Kopie) und speist erst DIESE in pg_combinebackup. BAK-01s Speicherformat selbst unverändert (kleinere, leichter prüfbare Dateien).
  2. pg_wal.tar.gz wurde nie verifiziert oder wiederhergestellt. pg_basebackups Standard-WAL-Methode (stream) erzeugt bei -Ft -z eine ZWEITE Archivdatei neben base.tar.gz — ohne sie ist keine crash-konsistente Wiederherstellung möglich (Postgres findet sonst keinen gültigen Checkpoint). backup.Verify prüft jetzt BEIDE Archive vollständig; backup.Restore extrahiert das WAL der ZULETZT gezogenen Stufe (nicht aller Stufen) nach pg_wal/ im Wiederherstellungsziel. Ergänzung zu BAK-01s Prüfung, keine Umstellung des Sicherungsverfahrens.
  3. Go-exec-Hänger bei pg_ctl start: pg_ctl startet Postgres als Hintergrundprozess, der die geerbten stdout/stderr-Pipes NICHT schließt — CombinedOutput() (wartet auf Pipe-EOF) hängt sich dadurch auf, obwohl pg_ctl selbst längst zurückgekehrt ist. Fix nur im Testcode: echte Logdatei (-l) statt Pipe, Standard-pg_ctl-Muster.

Zusätzlich real (nicht Ticket-relevant, aber notiert): auf diesem Debian-Postgres liegen postgresql.conf/pg_hba.conf NICHT in PGDATA (sondern /etc/postgresql/17/main/) — ein echtes Restore-Runbook muss diese Dateien separat mitführen/rekonstruieren, pg_basebackup sichert sie nicht. Für den Testnachweis minimal nachgereicht, kein Produktcode.

Umsetzung

  • internal/restore.AtomicRestore — Ablaufsteuerung, JSONL-Protokoll (Timestamp, Kind, Source, Target, Result).
  • internal/backup.Restorepg_combinebackup gegen extrahierte Plain-Verzeichnisse + WAL-Wiederherstellung der letzten Stufe.
  • internal/backup.extractTarGz (in verify.go, neben der bestehenden Tar/Gzip-Leselogik) — vollständige Extraktion, kein Kopf-only-Read.
  • internal/backup.Verify — prüft jetzt base.tar.gz UND pg_wal.tar.gz vollständig.
  • internal/objectbackup.Restorerestic restore --target.
  • cmd/restore-clidatabase/objects-Unterbefehle, -force-Flag (vor Positionsargumenten, Go-flag-Konvention), kein systemd-Timer (Notfall-/Einzelfall-Werkzeug, kein Zeitplan).

Prüfungen

# Prüfung Ergebnis
1 Restore auf leerem System vollständig und erfolgreich durchgeführt bestanden — real: TestRestore_CombinesIntoStartablePostgresInstance (Postgres tatsächlich aus wiederhergestelltem Verzeichnis gestartet, SELECT 1 über echte Verbindung beantwortet); zusätzlich real per restore-cli database auf 131 ausgeführt (PG_VERSION/base/pg_wal vorhanden, Exit 0); TestRestore_RecoversRealContentFromSnapshot (Objekt-Storage, echter Dateiinhalt verglichen) UND real per restore-cli objects auf 131 (echter restic-Snapshot wiederhergestellt)
2 Restore auf nicht-leeres Zielverzeichnis lässt bei Abbruch den ursprünglichen Inhalt unverändert bestandenTestAtomicRestore_NonEmptyTargetWithoutForce_LeavesOriginalUntouched (restoreFn wird nachweislich NIE aufgerufen) und TestAtomicRestore_FailedRestoreFn_LeavesTargetUntouched; real auf 131: restore-cli database gegen nicht-leeres Ziel ohne -force → Abbruch, bestehende Testdatei unverändert vorhanden
3 Protokolleintrag zum Restore ist vollständig und nachvollziehbar bestandenTestAtomicRestore_LogsCompleteEntry (Quelle, Ziel, Zeitpunkt, Ergebnis für Erfolgs- UND Abbruchfall in derselben Datei); real auf 131: /tmp/restore-cli-test.log zeigt alle vier realen Läufe (Abbruch, Fehler mangels PATH, Erfolg, Force-Überschreiben) korrekt protokolliert

Zusätzlich: TestAtomicRestore_EmptyTarget_Succeeds, TestAtomicRestore_ForceOverwritesNonEmptyTarget (bewusste Bestätigung ersetzt bestehenden Inhalt real, alter Inhalt nachweislich weg, neuer da).

Echte Verdrahtung auf 192.168.1.131

  • restore-cli gebaut nach /opt/nexarch-archive/bin/
  • Kein systemd-Timer (bewusst — Notfall-/Einzelfall-Werkzeug)
  • Vier reale CLI-Läufe durchgeführt und protokolliert: Abbruch bei nicht-leerem Ziel ohne -force, Fehler mangels pg_combinebackup im PATH (zeigt: Fehler wird korrekt erkannt UND protokolliert, kein stiller Fehlschlag), erfolgreicher Restore einer echten Generation (20260829T222054Z) in leeres Ziel, erfolgreiches Überschreiben mit -force. Objekt-Storage-Restore real gegen echten restic-Snapshot (43da36bf) — Inhalt (.placeholder-Datei) tatsächlich vorhanden.
  • Alle Testartefakte (/tmp/restore-*, Testlog) nach Prüfung entfernt.

Build/Test-Ergebnis

go build ./...  -> clean
go vet ./...    -> clean
golangci-lint run ./...  -> 0 issues

internal/restore: 5/5 Tests (reine Funktionen, kein Setup nötig). internal/objectbackup: TestRestore_RecoversRealContentFromSnapshot bestanden (zusammen mit den bestehenden BAK-02-Tests). internal/backup: TestRestore_CombinesIntoStartablePostgresInstance bestanden — Hinweis: dieser eine Test läuft NICHT als root (pg_ctl: cannot be run as root) und braucht pg_combinebackup/ pg_ctl im PATH (/usr/lib/postgresql/17/bin auf Debian, dort nicht standardmäßig verlinkt) — daher separat als postgres-Systemnutzer mit entsprechendem PATH ausgeführt, nicht Teil des root-make check-Laufs; dort wird er mit klarer Meldung übersprungen (pg_combinebackup nicht installiert), kein stiller Fehlschlag. Alle übrigen BAK-01-Tests (inkl. der um pg_wal.tar.gz erweiterten Verify) liefen unverändert grün im normalen make check-Lauf.

Gesamtergebnis

Bestanden. Alle drei Akzeptanzkriterien und alle drei Pflichtprüfungen real erfüllt — beide Restore-Pfade (Datenbank, Objekt-Storage) sowohl testgetrieben als auch manuell über restore-cli auf echter Infrastruktur nachgewiesen. Drei während der Implementierung gefundene reale Defekte (Tar-vs-Plain-Format, fehlende WAL-Sicherung/ -Verifikation, Go-exec-Pipe-Hänger) behoben und dokumentiert, nicht stillschweigend umgangen.