Nutzerfrage: DB- und Objekt-Rotation laufen unabhaengig, kein Test/ Invariant stellt sicher, dass aeltester erreichbarer Snapshot und aelteste erreichbare DB-Generation zeitlich zusammenpassen. Aktuell identische keep-Werte sind Zufall, kein erzwungenes Verhalten - als Folgepunkt dokumentiert, nicht Teil des Tickets.
6.3 KiB
BAK-07 – Prüfprotokoll: Aufbewahrungsfrist für Backup-Snapshots
Voraussetzung BAK-01, BAK-02 – erledigt, siehe eigene Protokolle.
Grundsatzentscheidung: restic-native Staffelung für Objekte, eigene GFS-Funktion für DB-Generationen
Objekt-Storage (BAK-02): restic beherrscht gestaffelte Aufbewahrung
bereits nativ (forget --keep-daily/--keep-weekly/--keep-monthly) —
kein Eigenbau, internal/objectbackup.PruneTiered ist ein dünner
Wrapper (dieselbe Disziplin wie beim ursprünglichen
restic-statt-Eigenbau-Entscheid in BAK-02).
Datenbank-Generationen (BAK-01): pg_basebackup kennt keine
Staffelung, daher internal/backup.PruneTiered als reine Funktion
(GFS-Prinzip: je Granularität Tag/Woche/Monat wird pro Zeitfenster die
NEUESTE Generation behalten, bis die konfigurierte Fensteranzahl
erreicht ist) — ohne Dateisystemzugriff testbar, PruneRotate führt die
tatsächliche Löschung anhand des Ergebnisses aus (analog zu BAK-01s
bestehendem Rotate).
Beide Policies sind über Umgebungsvariablen konfigurierbar
(NEXARCH_BACKUP_KEEP_DAILY/WEEKLY/MONTHLY,
NEXARCH_OBJECTBACKUP_KEEP_DAILY/WEEKLY/MONTHLY), nicht hart codiert
(Ticket-Vorgabe). Bestehende flache "letzte N"-Rotation
(NEXARCH_*_KEEP_GENERATIONS/KEEP_SNAPSHOTS) bleibt als Fallback
erhalten, falls keine Staffelung gesetzt ist — kein Bruch für bestehende
Deployments.
Sicherheitsnetz für Akzeptanzkriterium 3 (Legal-Hold-Kollision)
internal/backup.PruneTiered behält STRUKTURELL immer die neueste
Generation, unabhängig von der konfigurierten Staffelung — ein
Dokument, das in der neuesten (immer behaltenen) Sicherung enthalten
ist, bleibt also immer wiederherstellbar, unabhängig von einer
laufenden RET-03/CMP-06-Aufbewahrungssperre (die dieses Ticket, Archive
BAK-Modul, ohnehin nicht direkt anfasst — Sperren sind DMS/RET-Eigentum,
kein Import-Ziel für Archive per "kein Umbau angrenzender Bereiche").
Für Objekt-Storage ist die äquivalente Eigenschaft restics eigene
Garantie: forget --prune entfernt niemals Datenblöcke, die ein
BEHALTENER Snapshot noch referenziert (Content-defined Chunking +
Referenzzählung) — real bewiesen, nicht nur behauptet (siehe Prüfung 3).
Umsetzung
internal/backup.RetentionPolicy/PruneTiered(retention.go) — reine GFS-Funktion.internal/backup.PruneRotate(rotate.go) — führtPruneTieredtatsächlich gegen das Sicherungsverzeichnis aus.internal/objectbackup.RetentionPolicy/PruneTiered(restic.go) — Wrapper um restics native Staffelung.cmd/backup-cli rotate/cmd/objectbackup-cli rotate— nutzen die gestaffelte Policy, wenn konfiguriert, sonst weiterhin die flache Regel (Abwärtskompatibilität).
Prüfungen
| # | Prüfung | Ergebnis |
|---|---|---|
| 1 | Prune-Test mit simulierten Snapshots über mehrere Altersstufen ergibt korrekte Staffelung | bestanden — TestPruneTiered_KeepsCorrectStaggering (Datenbank, reine Funktion, simulierte Generationen über Tage/Wochen/Monate) UND TestPruneTiered_KeepsCorrectStaggering_RealSnapshots (Objekt-Storage, ECHTE restic-Snapshots mit erzwungenem Zeitstempel restic backup --time, KeepDaily=3 reduziert 10 reale Snapshots korrekt auf 3) |
| 2 | Automatisierte Löschung abgelaufener Snapshots im Test nachgewiesen | bestanden — TestPruneRotate_DeletesOnlyExpiredGenerations: reales Verzeichnis, abgelaufene Generation tatsächlich vom Dateisystem entfernt, aktuelle unangetastet; real auf 131: backup-cli rotate und objectbackup-cli rotate über systemd ausgelöst, Journal zeigt „gestaffelte rotation abgeschlossen“ |
| 3 | Legal-Hold-Test bestätigt, dass Pruning nicht mit Dokumenten-Retention kollidiert | bestanden — TestPruneTiered_AlwaysKeepsNewest (Datenbank-Sicherheitsnetz: neueste Generation immer behalten, selbst bei Policy 0/0/0) UND TestPruneTiered_KeptSnapshotStillFullyRestorable (Objekt-Storage, real: 5 ältere Snapshots eines Dokuments weggeprunt, verbleibender Snapshot danach vollständig und inhaltlich korrekt wiederhergestellt, zusätzlich restic check --read-data bestätigt Datenintegrität nach dem Pruning) |
Zusätzlich: TestPruneTiered_DeterministicForIdenticalInput.
Echte Verdrahtung auf 192.168.1.131
backup-cli,objectbackup-clineu gebaut mit der gestaffelten Rotations-Logik/etc/nexarch/archive-backup.envund/etc/nexarch/archive-objectbackup.envumKEEP_DAILY=14/KEEP_WEEKLY=12/KEEP_MONTHLY=12ergänzt- Beide
*-rotate.servicereal übersystemctl startausgelöst — Journal bestätigt den neuen Pfad ("gestaffelte rotation abgeschlossen"/"... entfernt: [...]"), nicht mehr die alte flache Regel
Build/Test-Ergebnis (192.168.1.131, make check)
go build ./... -> clean
go vet ./... -> clean
golangci-lint run ./... -> 0 issues
go test ./... -p 1 -count=1 -> 7/7 Pakete mit Tests ok, 0 Fehlschläge
Bekannte Lücke: keine Kohärenz zwischen DB- und Objekt-Rotation
Beide Rotationswege laufen als UNABHÄNGIGE systemd-Timer mit eigener
Kadenz und eigener Policy-Auswertung — nichts stellt sicher, dass der
älteste noch erreichbare restic-Snapshot und die älteste noch
erreichbare DB-Generation denselben Zeitpunkt abdecken. Die aktuelle
Konfiguration (KEEP_DAILY=14/KEEP_WEEKLY=12/KEEP_MONTHLY=12 auf
beiden Seiten identisch) ist eine ZUFÄLLIGE Übereinstimmung, kein
getestetes oder erzwungenes Invariant — ein restic-Snapshot ohne
zeitlich passende DB-Generation wäre ein inkonsistenter Restore-Punkt
(Objekte ohne zugehörige file_revisions-Metadaten oder umgekehrt).
Nicht Teil dieses Tickets (BAK-07 fordert nur je Sicherungsart eine
konfigurierbare Staffelung, kein Cross-Artefakt-Alignment) — als
dokumentierter Folgepunkt festgehalten, nicht stillschweigend
übergangen: ein künftiges Ticket müsste entweder die Policies fest
koppeln oder bei Restore-Zeitpunkt-Wahl (BAK-03) explizit warnen, wenn
kein zeitlich passendes Gegenstück mehr existiert.
Gesamtergebnis
Bestanden. Alle drei Akzeptanzkriterien und alle drei
Pflichtprüfungen real erfüllt — sowohl mit simulierten (Datenbank) als
auch mit tatsächlich zeitversetzt erzeugten, realen restic-Snapshots
(Objekt-Storage). Ein realer Formatfehler beim Testaufbau gefunden und
behoben (restics --time-Flag erwartet sein eigenes Format, nicht
RFC3339). Beide Rotations-Wege real über systemd auf 192.168.1.131
ausgelöst, nicht nur isolierter Testcode.