Datenbank-Migrationen in der Produktion ausführen, ohne den Schlaf zu verlieren
Die Deployment-Pipeline ist grün. Die Code-Änderungen wurden reviewed und genehmigt. Die Staging-Umgebung sieht gut aus. Dann kommt der Moment, den jeder Engineer fürchtet: die Migration gegen die Produktionsdatenbank auszuführen.
Hier liegt das eigentliche Risiko. Eine Schema-Änderung, die auf Ihrem Laptop einwandfrei funktioniert, kann eine Produktionstabelle für Minuten sperren. Eine Datenmigration, die mit tausend Zeilen problemlos läuft, kann bei einer Million Zeilen Stunden dauern. Und wenn etwas schiefgeht, liefert die Anwendung echten Benutzern Fehlermeldungen zurück.
Das Problem ist nicht nur technischer Natur. Es geht auch um Timing, Koordination und darum, zu wissen, wann man aufhören muss.
Wann die Migration ausgeführt werden sollte
Im Gegensatz zu Anwendungs-Deployments, die oft zu jeder Stunde durchgeführt werden können, haben Datenbank-Migrationen direkte Auswirkungen auf die Abfrageleistung und die Datenverfügbarkeit. Die sicherste Zeit für die Ausführung ist bei geringem Traffic. Viele Teams nennen dies ein Downtime-Fenster, aber dieser Name ist irreführend. Ein Downtime-Fenster bedeutet nicht, dass die Anwendung offline gehen muss. Es bedeutet, dass Sie sich auf einen Zeitraum geeinigt haben, in dem Sie Änderungen vornehmen können, die zu Störungen führen könnten, und dass Sie auf diese Möglichkeit vorbereitet sind.
Die eigentliche Frage ist: Kann Ihre Migration ausgeführt werden, während die Anwendung weiterhin Traffic bedient? Wenn Sie Ihre Migrationen sorgfältig entwerfen, lautet die Antwort oft ja. Das hängt jedoch davon ab, zu verstehen, was Ihre Datenbank tut, wenn Sie bestimmte Befehle ausführen.
Das Locking-Problem
Die häufigste Ursache für Probleme bei Produktionsmigrationen ist Locking. Wenn Sie einen Befehl wie ALTER TABLE ausführen, sperren die meisten Datenbanken die Tabelle, um andere Änderungen während des Vorgangs zu verhindern. Wenn dieser Befehl lange dauert, wartet oder scheitert jede Abfrage Ihrer Anwendung, die diese Tabelle betrifft. Benutzer sehen langsame Seiten oder Fehlermeldungen.
Einige Datenbanken bieten Möglichkeiten, das Locking zu reduzieren. PostgreSQL unterstützt CREATE INDEX CONCURRENTLY, das einen Index ohne Blockierung von Schreibvorgängen erstellt. MySQL hat ähnliche Optionen für bestimmte Operationen. Aber nicht jede Änderung kann ohne eine Sperre durchgeführt werden. Das Hinzufügen einer Spalte mit einem Standardwert, das Ändern eines Spaltentyps oder das Entfernen einer Spalte erfordert oft eine exklusive Sperre.
Der Schlüssel liegt darin, vor dem Schreiben der Migration zu wissen, was Ihre Datenbank unterstützt. Überprüfen Sie die Dokumentation für Ihre Datenbankversion. Testen Sie die Migration auf einer Kopie Ihrer Produktionsdaten, nicht nur auf einer kleinen Stichprobe. Messen Sie, wie lange die Sperre unter realistischen Bedingungen dauern würde.
Hier ist zum Beispiel ein sicheres Muster zum Hinzufügen einer Spalte mit einem Standardwert in PostgreSQL, das eine lange exklusive Sperre vermeidet:
-- Schritt 1: Spalte als nullable hinzufügen (schnell, kein Standardwert-Rewrite)
ALTER TABLE users ADD COLUMN display_name text;
-- Schritt 2: Spalte in kleinen Batches befüllen
UPDATE users SET display_name = username WHERE display_name IS NULL LIMIT 1000;
-- Wiederholen, bis keine Zeilen mehr übrig sind
-- Schritt 3: NOT NULL setzen (schnell, kein Daten-Rewrite)
ALTER TABLE users ALTER COLUMN display_name SET NOT NULL;
Große Migrationen in kleine Schritte aufteilen
Ein häufiger Fehler ist es, zu viel in einer Migration erledigen zu wollen. Zum Beispiel das Hinzufügen einer neuen Spalte, das Befüllen mit Daten aus einer anderen Spalte und das anschließende Löschen der alten Spalte in einem Schritt. Dies erzeugt einen langlaufenden Vorgang, der für die gesamte Dauer Sperren hält.
Ein sichererer Ansatz ist es, die Arbeit auf mehrere kleinere Migrationen aufzuteilen:
- Fügen Sie die neue Spalte ohne Standardwert hinzu.
- Führen Sie einen Hintergrundjob aus, um die neue Spalte in Batches zu befüllen.
- Überprüfen Sie, ob die Daten vollständig und korrekt sind.
- Löschen Sie die alte Spalte in einer separaten Migration.
Jeder Schritt kann überprüft werden, bevor Sie zum nächsten übergehen. Wenn in Schritt zwei etwas schiefgeht, haben Sie nichts verloren. Sie können die Daten korrigieren und es erneut versuchen. Dieser Ansatz dauert länger, ist aber weitaus weniger riskant.
Sicherheitschecks in die Pipeline einbauen
Ihre Pipeline sollte Migrationen nicht blind ausführen. Sie muss den aktuellen Zustand der Datenbank überprüfen, bevor sie Änderungen vornimmt. Bevor die Migration startet, kann die Pipeline nach langlaufenden Abfragen suchen. Wenn es Abfragen gibt, die länger als ein paar Sekunden laufen, sollte die Migration warten. Diese Abfragen könnten Sperren halten, die die Migration blockieren würden, oder die Migration könnte sie blockieren.
Das folgende Flussdiagramm veranschaulicht den oben beschriebenen Sicherheitscheck-Prozess:
Während der Migration sollte die Pipeline Folgendes überwachen:
- Ausführungsdauer: wie lange jede Anweisung dauert
- Lock-Wartezeit: ob andere Abfragen auf Sperren warten
- Fehlerrate: alle Fehler von der Datenbank oder Anwendung
Wenn einer dieser Metriken einen Schwellenwert überschreitet, sollte die Pipeline die Migration stoppen und das Team benachrichtigen. Hier geht es nicht um übertriebene Vorsicht. Es geht darum, einen klaren Mechanismus zu haben, um zu verhindern, dass aus einem kleinen Problem ein Produktionsvorfall wird.
Was passiert, nachdem die Migration abgeschlossen ist
Die Migration wurde ohne Fehler abgeschlossen. Die Pipeline zeigt grün. Aber die Arbeit ist noch nicht getan.
Ihre Anwendung könnte noch alte Datenbankverbindungen offen haben. Connection Pools cachen Verbindungen, und diese gecachten Verbindungen könnten Referenzen auf das alte Schema halten. Einige ORMs cachen Abfragepläne, die nicht mehr zum neuen Schema passen. Diese Probleme treten nicht immer sofort auf. Sie können Minuten oder Stunden später subtile Fehler verursachen.
Viele Teams starten die Anwendung nach einer Migration neu oder leeren zumindest alte Verbindungen und lassen neue erstellen. Andere warten ein paar Minuten, während sie Logs und Metriken überwachen, bevor sie die Migration für erfolgreich erklären. Der genaue Ansatz hängt von Ihrem Anwendungsstack und der Funktionsweise Ihres Connection Poolings ab.
Praktische Checkliste für Produktionsmigrationen
Bevor Sie eine Migration in der Produktion ausführen, gehen Sie diese Checkliste durch:
- Testen Sie die Migration auf einer Kopie der Produktionsdaten, nicht nur auf einer Entwicklungsdatenbank
- Messen Sie, wie lange jede Anweisung dauert und welche Sperren sie erwirbt
- Überprüfen Sie, ob die Datenbankversion sperrfreie Alternativen für Ihre Operation unterstützt
- Suchen Sie vor dem Start der Migration nach langlaufenden Abfragen
- Richten Sie ein Monitoring für Lock-Wartezeit und Fehlerrate während der Migration ein
- Definieren Sie eine klare Abbruchbedingung: welche Metrik oder welcher Fehler einen Stopp auslöst
- Bereiten Sie einen Rollback-Plan vor: wie die Migration rückgängig gemacht wird, wenn etwas schiefgeht
- Benachrichtigen Sie das Team vor und nach der Migration
- Planen Sie, die Anwendung nach der Migration neu zu starten oder Verbindungen zu aktualisieren
Das Fazit
Eine Datenbank-Migration in der Produktion auszuführen bedeutet nicht, Risiken zu vermeiden. Es geht darum, das Risiko zu verstehen, es in handhabbare Teile zu zerlegen und klare Signale dafür zu haben, wann man aufhören muss. Die besten Migrationen sind die, die niemand bemerkt, weil sie reibungslos verlaufen sind. Die zweitbesten sind die, die frühzeitig gestoppt wurden, bevor sie echten Schaden anrichteten. Die schlimmsten sind die, die bis zum Ende durchliefen, aber die Anwendung auf eine Weise beschädigten, die Stunden dauerte, um sie zu erkennen. Bauen Sie Ihre Pipeline so, dass sie auf die erste Kategorie abzielt, aber seien Sie immer auf die zweite vorbereitet.