Business & Strategie

Guenstig deployen, teuer im Oncall bezahlen

Ein MVP mit kleinem Budget muss nicht auf einer teuren Plattform laufen. Es muss aber einen Ausfall überstehen können, ohne dass der diensthabende DevOps Engineer erst herausfindet, ob die Daten überhaupt zurückkommen. Meine Position: Spart an Infrastruktur, solange ihr eine Wiederherstellung regelmäßig vorführt. Könnt ihr das nicht, ist selbst die billigste Produktionsumgebung zu teuer.

Eine günstige Datenbank ist nur günstig, wenn ein Restore gelingt

Bei einem jungen Dienst landet PostgreSQL oft auf derselben virtuellen Maschine wie die Anwendung. Das kann eine vernünftige Entscheidung sein, weil eine VM, PostgreSQL 16 und ein systemd-Service weniger bewegliche Teile haben als eine größere Plattform. Der Preisvorteil gilt allerdings nur, wenn ein Defekt dieser Maschine innerhalb der vereinbarten Ausfallgrenze behoben werden kann. Eine vorhandene Backup-Datei belegt das nicht: Sie sagt weder etwas über ihre Lesbarkeit noch über Rollen, Erweiterungen oder die Zeit bis zum ersten erfolgreichen Request.

Der Beitrag Billig bauen oder richtig bauen Was kostet dein MVP wirklich stellt die Baukostenfrage; für den Produktionsbetrieb würde ich sie enger stellen, weil ein niedriger Monatspreis ohne nachgewiesenen Rückweg keine belastbare Ersparnis ist. Als zu vereinbarenden Grenzwert würde ich für einen ersten internen Dienst beispielsweise ein RPO von 24 Stunden festlegen: Im schlimmsten Fall darf ein Tag Daten verloren gehen. Für ein öffentlich verkauftes Produkt kann das inakzeptabel sein, weil bereits bestätigte Änderungen dann wieder verschwinden würden. Die Zahl ist keine Best Practice, sondern eine Entscheidung, die Produktverantwortliche ausdrücklich unterschreiben müssen.

Ein pg_dump ist für eine kleine Datenbank ein brauchbarer Start, weil PostgreSQL damit einen konsistenten logischen Export erzeugt. Er ersetzt keine Point-in-Time-Recovery, weil sich mit einem täglichen Dump nicht auf den Zustand unmittelbar vor einem Bedienfehler zurückspringen lässt. Kopiert die Archive deshalb an einen getrennten Ort, etwa in einen S3-Bucket mit eingeschränkten Schreibrechten; liegt die einzige Kopie auf derselben VM, kann ein voller oder verlorener Datenträger Anwendung und Sicherung zugleich beseitigen. Aktiviert für den Bucket eine Aufbewahrungsregel und prüft die Löschberechtigungen, weil ein erfolgreiches Hochladen keinen Schutz gegen versehentliches Löschen darstellt.

Die folgende Probe läuft mit PostgreSQL-Client-Werkzeugen auf einem Host, der die nötigen Datenbankrechte besitzt. SOURCE_DB benennt die Quelldatenbank; public.orders ist eine bewusst konkrete fachliche Tabelle und muss an euer Schema angepasst werden. Führt die Probe in einer abgeschotteten Umgebung aus, weil der Restore produktive Daten enthalten kann.

set -euo pipefail
: "${SOURCE_DB:?SOURCE_DB setzen}"
target="restore_probe_$$"
archive="$(mktemp)"
cleanup() { dropdb --if-exists "$target"; rm -f "$archive"; }
trap cleanup EXIT
pg_dump -Fc -f "$archive" "$SOURCE_DB"
createdb "$target"
pg_restore --exit-on-error --no-owner -d "$target" "$archive"
psql -X -v ON_ERROR_STOP=1 -d "$target" -Atqc 'SELECT count(*) FROM public.orders;'

Dieser Test ist absichtlich schmal: Er beweist, dass sich genau dieser Export einspielen und eine fachliche Tabelle lesen lässt, nicht dass die gesamte Anwendung wieder funktioniert. Messt zusätzlich die Zeit vom Start der Probe bis zu einem erfolgreichen HTTP-Request gegen die wiederhergestellte Umgebung, weil das vereinbarte RTO den nutzbaren Dienst und nicht bloß einen beendeten pg_restore-Prozess meint. Haltet den gemessenen Wert mit Datum, Archivgröße und PostgreSQL-Version fest; ohne diese Angaben lässt sich eine spätere Verschlechterung kaum erklären.

Die Einzel-VM gewinnt nur bei verhandelbarer Ausfallzeit

Die eigentliche Budgetentscheidung lautet hier PostgreSQL 16 auf einer Einzel-VM gegen Managed PostgreSQL mit Point-in-Time-Recovery, etwa Amazon RDS for PostgreSQL. Die Einzel-VM gewinnt, wenn Datenmenge und Team klein sind, ein längerer Ausfall ausdrücklich akzeptiert wird und jemand Restore sowie Sicherheitsupdates betreuen kann. Sie kostet weniger laufende Infrastruktur, verlangt aber Arbeitszeit für Backups, Patches, Ersatzhardware und Übungen. Managed PostgreSQL gewinnt, wenn kurze Wiederanlaufzeiten oder ein kleiner Datenverlust vertraglich zählen, weil automatisierte Sicherungen und ein definierter Restore-Pfad die eigene Betriebsarbeit reduzieren. Es kostet mehr laufendes Geld und weiterhin Zeit für Tests, Zugriffsrechte und Anwendungskonsistenz.

Für eine Entscheidungsvorlage würde ich beide Wege mit denselben Annahmen rechnen, statt Preislisten nebeneinanderzustellen. Als Rechenbeispiel, nicht als Anbieterpreis: Kostet die Einzel-VM 45 Euro und der verwaltete Datenbankdienst 140 Euro pro Monat, beträgt der Infrastrukturunterschied 95 Euro. Wenn die Eigenlösung monatlich drei Stunden für Patchen, Backup-Kontrolle und Restore-Probe benötigt, entscheidet der interne Stundensatz bereits über den scheinbaren Preisvorteil. Diese Stunden müssen als geplante Arbeit erfasst werden, weil sie sonst erst während eines Incidents im Budget auftauchen.

Auch ein verwalteter Dienst macht die Anwendung nicht automatisch wiederherstellbar. Ein Restore von Amazon RDS erzeugt eine Datenbankinstanz, aber er korrigiert weder falsch gesetzte Secrets noch eine Migration, die mit dem älteren Datenstand nicht kompatibel ist. Testet deshalb den Weg bis zur Anwendung: neue Verbindungszeichenfolge setzen, den Dienst starten, einen lesenden Request senden und eine harmlose Schreiboperation prüfen. Für diesen Test reicht bei einem kleinen Dienst eine kurze Checkliste im Repository; ein umfangreiches Runbook ohne ausgeführte Schritte wäre weniger wert, weil es seine eigene Korrektheit nicht belegt.

Den Beitrag Du bist Backend Dev und jetzt Oncall Der versteckte Wartungsballast würde ich an dieser Stelle um eine unbequeme Einschränkung ergänzen: Nicht jede selbst betriebene Komponente ist unnötiger Ballast, sofern ihr Ausfallpfad geübt und ihre Ausfallzeit akzeptiert ist. Ich würde keine billige Eigenlösung wählen, bei der niemand einen Restore ausführen darf, weil eine Sicherung ohne zugängliche Zugangsdaten im Ernstfall praktisch nicht existiert. Ebenso würde ich kein Managed-Angebot allein wegen des Wortes „automatisch“ freigeben, weil dessen Restore-Zeit erst unter euren Datenmengen und Netzbedingungen relevant wird.

Ein Alarm muss eine Entscheidung auslösen können

Ein knappes Budget rechtfertigt wenige Signale, aber keine blinden Signale. Für einen kleinen HTTP-Dienst genügen zunächst ein externer Verfügbarkeitstest, eine Kennzahl für fehlgeschlagene Requests, die freie Kapazität des Datenträgers und das Ergebnis der letzten Restore-Probe. Prometheus kann Metriken erfassen, Alertmanager kann Benachrichtigungen zustellen und Grafana kann Verläufe zeigen; diese Kombination lohnt sich nur, wenn jemand die Alerts pflegt, weil unbestätigte Warnungen die Reaktion auf einen echten Ausfall erschweren.

Legt für jeden Pager-Alert vorab die Handlung fest. Bei „Datenträger fast voll“ lautet sie beispielsweise: Wachstum von PostgreSQL-Daten, WAL und Logs unterscheiden und dann gezielt Platz schaffen oder Kapazität erweitern. Ein Alarm auf hohe CPU-Auslastung allein ist schwächer, weil CPU-Spitzen auch bei erfolgreichen Requests auftreten können. Für die Nutzerwirkung ist eine fehlgeschlagene HTTP-Anfrage oft aussagekräftiger; RFC 9110 definiert den Statuscode 503 für vorübergehend nicht verfügbare Dienste, weshalb sich dieser Code in einem externen Check eindeutig auswerten lässt.

Als einzustellenden Schwellenwert könnt ihr eine Benachrichtigung auslösen, wenn der externe Check fünf Minuten lang keine erfolgreiche Antwort erhält. Die Dauer muss zu eurer Bereitschaft passen, weil ein kurzer Deploy sonst unnötig jemanden weckt und ein zu langer Zeitraum einen wirklichen Ausfall verdeckt. Trennt Warnungen von Pager-Alerts: Ein fehlgeschlagener einzelner Restore-Test gehört sofort sichtbar ins Team, aber erst ein nicht mehr erreichbarer Dienst oder ein unmittelbar gefährdetes RPO muss zwingend die Rufbereitschaft unterbrechen. Diese Trennung reduziert Alarmmüdigkeit, weil nicht jede Abweichung dieselbe Dringlichkeit hat.

OpenTelemetry-Traces sind hilfreich, wenn ein Request mehrere Dienste durchläuft und der Fehlerort sonst unklar bleibt. Für einen einzelnen API-Prozess mit PostgreSQL würde ich sie nicht vor Restore-Test und externem Check einführen, weil zusätzliche Telemetrie den Verlust der einzigen Datenbank nicht behebt. Erfasst stattdessen zunächst das p95 der Request-Dauer, die Fehlerrate und die Dauer der letzten erfolgreichen Wiederherstellung; erst wenn diese Werte eine konkrete Frage offenlassen, lohnt sich feinere Instrumentierung. Docker Compose v2 kann für eine Testumgebung aus Anwendung und Datenbank reichen, weil dort reproduzierbare Startbefehle wichtiger sind als ein aufwendiger Orchestrierungsaufbau.

Eine Ausfallgrenze ist wertvoller als eine lange Einkaufsliste

„Wir machen es später richtig“ ist keine Betriebsvereinbarung, weil niemand daraus ableiten kann, wann die Sparvariante beendet werden muss. Schreibt für die gewählte Architektur stattdessen Abbruchkriterien auf. Ein Beispiel: Das Team akzeptiert zunächst ein RTO von vier Stunden, prüft aber nach jeder Restore-Probe den tatsächlichen Wert. Überschreitet die Probe diese Grenze, muss entweder der Ablauf vereinfacht, mehr Kapazität bereitgestellt oder auf einen Dienst mit schnellerem Restore gewechselt werden. Die vier Stunden sind eine verhandelbare Vorgabe, keine Zusage, die aus der Wahl eines Produkts folgt.

Dasselbe gilt für Datenwachstum. Ein logischer Dump, dessen Erstellung und Einspielen bei kleiner Datenbank bequem in ein Wartungsfenster passen, kann mit wachsender Datenmenge das RTO sprengen, weil Export und Import Zeit und freien Speicher benötigen. Prüft daher nach jeder Probe Archivgröße, Laufzeit und den freien Platz auf dem Restore-Ziel. Wenn der Abstand zur Ausfallgrenze schrumpft, testet WAL-Archivierung und Point-in-Time-Recovery oder vergleicht ein Managed-Angebot mit euren Messwerten. Ein Wechsel nach gemessener Laufzeit ist belastbarer als ein Wechsel nach Bauchgefühl, weil die Engstelle bereits bekannt ist.

Haltet außerdem fest, wer im Incident entscheiden darf. Eine technisch mögliche Wiederherstellung kann warten müssen, wenn unklar ist, ob der neuere Datenstand überschrieben werden darf. Definiert deshalb den Zeitpunkt, an dem Schreibzugriffe gestoppt werden, wer den Restore freigibt und wie Nutzer über möglicherweise verlorene Änderungen informiert werden. Diese wenigen Sätze sind keine Prozessdekoration, weil sie eine irreversible Entscheidung aus der hektischsten Phase des Ausfalls herausnehmen.

Beginne mit der letzten vorhandenen Sicherung: Spiele sie diese Woche in eine isolierte Datenbank ein und starte die Anwendung dagegen. Notiere Startzeit, Zeitpunkt des ersten erfolgreichen Schreib- und Lesezugriffs sowie jeden manuellen Eingriff. Scheitert die Probe, kaufe noch kein neues Monitoring. Behebe zuerst den konkreten Restore-Fehler, denn genau dort verläuft die Grenze zwischen einer günstigen Produktionslösung und einer bloß günstigen Rechnung.