managarten/docs/POSTGRES_BACKUP.md
Till JS 39526918a3 feat(infra): add pgBackRest for PostgreSQL Point-in-Time Recovery
Replace simple pg_dumpall with pgBackRest PITR backup system.
This enables recovery to any second, not just the last daily dump.

Configuration:
- docker/postgres/postgresql.conf: WAL archiving + performance tuning
  (shared_buffers=512MB, effective_cache_size=2GB for 16GB Mac Mini)
- docker/postgres/pgbackrest.conf: stanza config + retention policy

Docker (docker-compose.macmini.yml):
- postgres: mount custom config, enable WAL archiving
- postgres-backup: new pgBackRest container
  - Storage: /Volumes/ManaData/backups/pgbackrest
  - Retention: 4 full + 14 differential (~4 weeks)
  - Compression: Zstandard (zst)

Backup Schedule:
- 03:00 daily: Full backup
- Every 6h: Differential (changes since last full)
- Every hour: Incremental (changes since last backup)
- Continuous: WAL archiving (every 60s)

Documentation (docs/POSTGRES_BACKUP.md):
- Complete restore procedures (full, PITR, single DB)
- First-time setup instructions
- Monitoring and alerting integration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 11:18:33 +01:00

5.2 KiB

PostgreSQL Backup mit pgBackRest

Point-in-Time Recovery (PITR) für alle Mana-Datenbanken

Übersicht

Aspekt Vorher (pg_dumpall) Jetzt (pgBackRest)
Recovery Point Letzter Dump (24h Verlust möglich) Bis auf die letzte Sekunde
Backup-Typ Nur Full Dump Full + Differential + Incremental
Kompression Keine Zstandard (zst)
Parallelisierung Nein Ja (2 Threads)
Validierung Keine Automatisch

Architektur

PostgreSQL (mana-infra-postgres)
    │
    ├── WAL-Archivierung → pgBackRest empfängt WAL-Segmente
    │
    └── Scheduled Backups:
        ├── 03:00 → Full Backup (alle Daten)
        ├── 09:00, 15:00, 21:00 → Differential (nur Änderungen seit Full)
        └── Jede Stunde → Incremental (nur Änderungen seit letztem Backup)

Backup-Speicherort

/Volumes/ManaData/backups/pgbackrest/
├── archive/          # WAL-Archive (kontinuierlich)
└── backup/           # Full + Diff + Incr Backups

Retention: 4 Full Backups + 14 Differentials (~4 Wochen Rückblick)

Befehle

Status prüfen

# Backup-Info anzeigen
docker exec mana-infra-pgbackrest pgbackrest --stanza=mana info

# Letzte Backups auflisten
docker exec mana-infra-pgbackrest pgbackrest --stanza=mana info --output=json | jq '.[] | .backup[] | {label, type, timestamp_start, database_size}'

Manuelles Backup

# Full Backup (manuell)
docker exec mana-infra-pgbackrest pgbackrest --stanza=mana --type=full backup

# Differential Backup
docker exec mana-infra-pgbackrest pgbackrest --stanza=mana --type=diff backup

# Incremental Backup
docker exec mana-infra-pgbackrest pgbackrest --stanza=mana --type=incr backup

Backup validieren

# Verify prüft Integrität aller Backups
docker exec mana-infra-pgbackrest pgbackrest --stanza=mana verify

Wiederherstellung (Restore)

Szenario 1: Komplette Wiederherstellung (letzter Stand)

# 1. PostgreSQL stoppen
docker compose -f docker-compose.macmini.yml stop postgres

# 2. Altes Datenverzeichnis sichern
sudo mv /Volumes/ManaData/postgres /Volumes/ManaData/postgres.broken

# 3. Neues Verzeichnis erstellen
sudo mkdir -p /Volumes/ManaData/postgres
sudo chown 70:70 /Volumes/ManaData/postgres

# 4. Restore ausführen
docker exec mana-infra-pgbackrest pgbackrest --stanza=mana restore

# 5. PostgreSQL starten
docker compose -f docker-compose.macmini.yml start postgres

# 6. Prüfen
docker exec mana-infra-postgres psql -U postgres -c "\l"

Szenario 2: Point-in-Time Recovery (z.B. auf 14:59 Uhr)

# 1. PostgreSQL stoppen
docker compose -f docker-compose.macmini.yml stop postgres

# 2. Altes Datenverzeichnis sichern
sudo mv /Volumes/ManaData/postgres /Volumes/ManaData/postgres.broken

# 3. Neues Verzeichnis erstellen
sudo mkdir -p /Volumes/ManaData/postgres
sudo chown 70:70 /Volumes/ManaData/postgres

# 4. PITR Restore auf bestimmten Zeitpunkt
docker exec mana-infra-pgbackrest pgbackrest --stanza=mana \
  --type=time \
  --target="2026-03-24 14:59:00+01" \
  restore

# 5. PostgreSQL starten
docker compose -f docker-compose.macmini.yml start postgres

Szenario 3: Einzelne Datenbank wiederherstellen

pgBackRest stellt immer den gesamten PostgreSQL-Cluster wieder her. Für einzelne Datenbanken:

# 1. Restore in temporäres Verzeichnis
docker exec mana-infra-pgbackrest pgbackrest --stanza=mana \
  --pg1-path=/tmp/pg-restore \
  restore

# 2. Temporären PostgreSQL starten und pg_dump auf einzelne DB
docker run --rm -v /tmp/pg-restore:/var/lib/postgresql/data \
  postgres:16-alpine pg_dump -U postgres chat_db > /tmp/chat_db.sql

# 3. In laufende DB importieren
cat /tmp/chat_db.sql | docker exec -i mana-infra-postgres psql -U postgres chat_db

Monitoring

In Grafana (geplant)

pgBackRest exportiert keine nativen Prometheus-Metriken. Monitoring via:

# Health-Check in health-check.sh hinzufügen:
LAST_BACKUP=$(docker exec mana-infra-pgbackrest pgbackrest --stanza=mana info --output=json 2>/dev/null | jq -r '.[0].backup[-1].timestamp_stop // "never"')
echo "Last backup: $LAST_BACKUP"

Alerting

Der Health-Check (scripts/mac-mini/health-check.sh) kann prüfen ob das letzte Backup älter als 24h ist und einen Telegram-Alert senden.

Erste Einrichtung

Beim ersten Start auf dem Server:

# 1. Backup-Verzeichnis erstellen
sudo mkdir -p /Volumes/ManaData/backups/pgbackrest
sudo chown 999:999 /Volumes/ManaData/backups/pgbackrest

# 2. Container starten
docker compose -f docker-compose.macmini.yml up -d postgres postgres-backup

# 3. Stanza erstellen (einmalig)
docker exec mana-infra-pgbackrest pgbackrest --stanza=mana stanza-create

# 4. Erstes Full Backup
docker exec mana-infra-pgbackrest pgbackrest --stanza=mana --type=full backup

# 5. Prüfen
docker exec mana-infra-pgbackrest pgbackrest --stanza=mana info

Konfigurationsdateien

Datei Zweck
docker/postgres/postgresql.conf WAL-Archivierung + Performance-Tuning
docker/postgres/pgbackrest.conf pgBackRest Stanza + Retention
docker-compose.macmini.yml Container-Definition (postgres-backup)