PostgreSQL-Backups, die man wirklich zurückspielen kann: pgBackRest einrichten
Die meisten kleinen Postgres-Setups, die ich von Kunden übernehme, haben irgendeine Variante desselben Backups: ein Cronjob, der jede Nacht pg_dump laufen lässt, durch gzip schickt und die Datei in einem Ordner ablegt, manchmal noch per rclone extern gesichert. Es sieht aus wie ein Backup. Es läuft jede Nacht ohne Fehler. Und seit langer Zeit hat niemand versucht, daraus etwas wiederherzustellen.
Genau das ist das Problem. pg_dump ist als Grundlage in Ordnung, aber sobald eine Datenbank über ein paar GB wächst, stößt man schnell an echte Grenzen: Der Dump dauert jeden Monat länger, er erzeugt für die gesamte Dauer zusätzliche Last, und eine einzelne Dump-Datei gibt einem genau einen Wiederherstellungspunkt pro Tag. Geht am Nachmittag etwas schief, landet man beim Stand von letzter Nacht und verliert alles seitdem. Es gibt kein Point-in-Time-Recovery, und dump-basierte Backups skalieren ab einigen zehn GB nicht mehr gut.
Umstieg auf pgBackRest
pgBackRest löst das meiste davon, ohne viel zusätzliche Infrastruktur. Es macht volle, differentielle und inkrementelle Backups, archiviert WAL durchgehend für Point-in-Time-Recovery, unterstützt parallele Verarbeitung für schnellere Backups auf Multi-Core-Maschinen, und kann nach lokalem Speicher, S3-kompatiblem Storage oder beidem schreiben.
Für einen einzelnen kleinen Server sieht mein Setup ungefähr so aus:
# /etc/pgbackrest/pgbackrest.conf
[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
repo1-retention-diff=4
[main]
pg1-path=/var/lib/postgresql/16/main
pg1-port=5432
Dann WAL-Archivierung in postgresql.conf aktivieren:
archive_mode = on
archive_command = 'pgbackrest --stanza=main archive-push %p'
Stanza anlegen und das erste volle Backup ausführen:
sudo -u postgres pgbackrest --stanza=main stanza-create
sudo -u postgres pgbackrest --stanza=main --type=full backup
Danach hält ein Rhythmus von einem vollen Backup pro Woche und inkrementellen Backups an den restlichen Tagen die Größe des Repositorys im Rahmen, während trotzdem jeden Tag ein Wiederherstellungspunkt entsteht, plus durchgehendes WAL für alles, was feingranularer sein soll:
# Sonntag: volles Backup
0 2 * * 0 postgres pgbackrest --stanza=main --type=full backup
# Mo-Sa: inkrementell
0 2 * * 1-6 postgres pgbackrest --stanza=main --type=incr backup
Der Teil, den fast alle überspringen
Das alles bringt nichts, wenn man daraus noch nie wiederhergestellt hat. Ich halte mir extra einen kleinen zweiten VPS dafür: einmal im Monat zieht er sich das aktuellste Backup, führt pgbackrest --stanza=main --delta restore aus, startet Postgres, und ich lasse ein paar Plausibilitätsabfragen darauf laufen, Zeilenanzahlen der größten Tabellen, ein aktueller updated_at-Zeitstempel. Das dauert insgesamt vielleicht zwanzig Minuten, und ich schreibe mir auf, wie lange der Restore selbst gebraucht hat.
Diese Zahl, die tatsächliche Restore-Zeit, ist die echte Recovery Time Objective, nicht das, was irgendwo in einem Runbook steht. Beim ersten Mal, als ich das für einen Kunden gemacht habe, hat der Restore deutlich länger gedauert als angenommen, einfach wegen des Disk-Durchsatzes auf dem Ziel-VPS. Besser, man findet das an einem ruhigen Nachmittag heraus als während eines echten Ausfalls.
Was das für externe Kopien bedeutet
repo1 kann direkt auf S3-kompatiblen Speicher zeigen, Backblaze B2 und Wasabi funktionieren beide gut und sind günstig genug, dass Kosten kein Grund sind, das auszulassen. Bei allem, wofür ich tatsächlich nachts wach liegen würde, läuft eine zweite Repo-Definition auf externen Speicher, sodass ein kompletter Ausfall des Hosts, nicht nur ein missglücktes Deployment, nicht auch noch die Backups mitnimmt.
Ein Backup, das man nicht zurückgespielt hat, ist eine Hoffnung, kein Backup. pgBackRest bringt einen mit einer Konfigurationsdatei und ein paar Cron-Einträgen den größten Teil des Wegs zu "wirklich wiederherstellbar"; die Restore-Übung schließt die letzte Lücke.