BAK-01: datenbank-backup-strategie
Neues Modul-Verzeichnis code/archive/ (Monorepo-Muster wie code/dms/). PostgreSQL-17-natives inkrementelles Backup (pg_basebackup --incremental, WAL-Summarization) statt WAL-Archiving, um den geteilten Testhost ohne Neustart umzustellen (summarize_wal=on per pg_reload_conf). Rolle nexarch_backup mit REPLICATION-Attribut angelegt. internal/backup: FullBackup/IncrementalBackup (pg_basebackup-Wrapper), Verify (vollstaendiges Lesen von base.tar.gz, gzip+tar, nicht nur Header), Rotate/ListGenerations (generationsbasiert, aeltere zuerst entfernt). cmd/backup-cli fuer systemd-Timer-Aufruf (deploy/systemd/ nexarch-archive-backup-*.timer, taeglich/stuendlich/taeglich). Auf 192.168.1.131 verifiziert: 4/4 Tests gegen echte Postgres-17-Instanz (kein Mock) - inkrementelle Sicherung real kleiner als Vollsicherung, Verifikation erkennt absichtlich beschaedigte Datei, Rotation entfernt nur die aeltesten Generationen. Zusaetzlich ECHT verdrahtet: backup-cli gebaut, 3 systemd-Timer installiert+aktiviert, jeder der drei Dienste einmal ueber systemctl start end-to-end ausgeloest (status=0/SUCCESS je Dienst) - nicht nur go test, sondern der reale Automatisierungspfad selbst geprueft. Siehe archive/docs/BAK-01-PRUEFPROTOKOLL.md fuer alle Pruefungsergebnisse. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
c895a67c4b
commit
5e4b91c4e9
@@ -0,0 +1,56 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// ListGenerations liefert alle Generation-IDs in backupDir, aufsteigend
|
||||
// sortiert (die GenerationID selbst ist chronologisch sortierbar, siehe
|
||||
// NewGenerationID — kein Blick auf Dateisystem-Zeitstempel nötig, die bei
|
||||
// einem Restore/Kopiervorgang verändert werden könnten).
|
||||
func ListGenerations(backupDir string) ([]string, error) {
|
||||
entries, err := os.ReadDir(backupDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("backup: sicherungsverzeichnis lesen: %w", err)
|
||||
}
|
||||
var generations []string
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
generations = append(generations, e.Name())
|
||||
}
|
||||
}
|
||||
sort.Strings(generations)
|
||||
return generations, nil
|
||||
}
|
||||
|
||||
// Rotate entfernt alle bis auf die `keep` NEUESTEN Generationen
|
||||
// (Akzeptanzkriterium 3) — jede Generation umfasst ihre Vollsicherung UND
|
||||
// alle davon abhängigen Inkremente, ein Löschen der gesamten
|
||||
// Generationsverzeichnisses entfernt beides konsistent zusammen.
|
||||
func Rotate(backupDir string, keep int) (removed []string, err error) {
|
||||
if keep < 0 {
|
||||
keep = 0
|
||||
}
|
||||
generations, err := ListGenerations(backupDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(generations) <= keep {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
toRemove := generations[:len(generations)-keep]
|
||||
for _, gen := range toRemove {
|
||||
if err := os.RemoveAll(filepath.Join(backupDir, gen)); err != nil {
|
||||
return removed, fmt.Errorf("backup: generation %q entfernen: %w", gen, err)
|
||||
}
|
||||
removed = append(removed, gen)
|
||||
}
|
||||
return removed, nil
|
||||
}
|
||||
Reference in New Issue
Block a user