Datenbankschemata ändern ohne Produktionsausfall
Sie haben eine Datenbank, die seit fünf, zehn oder fünfzehn Jahren in Betrieb ist. Sie enthält Millionen von Transaktionen, Tausende von Tabellen und Hunderte von gespeicherten Prozeduren, die von Leuten geschrieben wurden, die vielleicht nicht mehr im Unternehmen arbeiten. Jedes Mal, wenn jemand eine Spalte hinzufügen, einen Datentyp ändern oder einen Index reparieren muss, stellt sich dieselbe Frage: „Wenn diese Migration fehlschlägt, wie lange dauert die Wiederherstellung?“
In solchen Organisationen ist die Datenbank nicht nur ein Ort zum Speichern von Daten. Sie ist das operative Herz des Geschäfts. Wenn die Anwendung ausfällt, können die Nutzer warten. Wenn die Datenbank korrupt wird, können Daten dauerhaft verloren gehen. Deshalb werden Schema- und Datenänderungen oft als risikoreiche Aktivitäten behandelt, die für Samstagnacht um 2 Uhr morgens geplant werden, in der Hoffnung, dass niemand etwas bemerkt, bis Montagmorgen.
Dieser Ansatz skaliert nicht. Je schneller ein Team Funktionen ausliefern will, desto häufiger sind Datenbankänderungen erforderlich. Wenn jede Änderung auf ein wöchentliches Wartungsfenster warten muss, ist das Produktteam frustriert. Werden Änderungen jedoch nachlässig durchgeführt, wird Datenkorruption zu einem echten Risiko.
Die eigentliche Frage ist nicht: „Welches Migrationstool ist das beste?“ Sondern: „Wie ändere ich ein Datenbankschema, ohne den Dienst zu unterbrechen, und wie komme ich zurück, wenn etwas schiefgeht?“
Sichere Migration beginnt mit kleinen Schritten
Das Kernprinzip ist einfach: Jede Änderung muss möglich sein, ohne die Verbindung zu laufenden Anwendungen zu unterbrechen, und sie muss umkehrbar sein, ohne Daten zu verlieren. Das bedeutet, dass Schemaänderungen in mehreren kleinen Schritten erfolgen müssen, nicht in einem großen Sprung.
Nehmen wir das Hinzufügen einer neuen Spalte. In einer Legacy-Datenbank fügen Sie die Spalte mit einem Standardwert hinzu oder machen sie nullable. Sie fügen nicht sofort strenge Constraints hinzu. Alte Anwendungsinstanzen laufen weiter, weil sie die neue Spalte nicht lesen. Neue Anwendungsinstanzen beginnen, in sie zu schreiben. Nachdem alle Instanzen aktualisiert wurden und stabil laufen, fügen Sie in einer separaten Migration Constraints wie NOT NULL oder Fremdschlüssel hinzu. Wenn zwischendurch etwas schiefgeht, ist das Zurücksetzen so einfach wie das Ignorieren der neuen Spalte. Kein Grund, Tabellen zu löschen oder aus Backups wiederherzustellen.
Das folgende Sequenzdiagramm veranschaulicht den oben beschriebenen sicheren, schrittweisen Prozess:
Das gleiche Muster gilt für die Änderung eines Datentyps. Angenommen, eine Spalte price ist derzeit INTEGER, soll aber DECIMAL werden. Der sichere Ansatz: Fügen Sie eine neue Spalte namens price_decimal hinzu, befüllen Sie sie mit konvertierten Werten aus der alten Spalte, lassen Sie die Anwendung aus der neuen Spalte lesen, während sie weiterhin in beide schreibt, und löschen Sie dann die alte Spalte, sobald alles stabil ist. Beim Zurücksetzen liest die Anwendung wieder aus der alten Spalte, die noch existiert.
Das folgende SQL-Beispiel zeigt die Vorwärts- und Rollback-Skripte für das sichere Hinzufügen einer Spalte:
-- Forward-Migration 1: Spalte als nullable hinzufügen
ALTER TABLE products ADD COLUMN discount_rate DECIMAL(5,2) NULL;
-- Daten nachträglich befüllen (nachdem die Anwendung in die neue Spalte schreibt)
UPDATE products SET discount_rate = 0.00 WHERE discount_rate IS NULL;
-- Forward-Migration 2: NOT NULL-Constraint hinzufügen
ALTER TABLE products ALTER COLUMN discount_rate SET NOT NULL;
-- Rollback-Skript (macht beide Schritte rückgängig)
ALTER TABLE products ALTER COLUMN discount_rate DROP NOT NULL;
ALTER TABLE products DROP COLUMN discount_rate;
Komplexe Änderungen benötigen parallele Läufe
Bei komplexeren Änderungen wie dem Aufteilen einer Tabelle in zwei oder dem Zusammenführen mehrerer Tabellen wird die Technik des parallelen Laufs angewendet. Die Anwendung schreibt gleichzeitig in die alte und die neue Struktur, während Leseabfragen schrittweise umgestellt werden. Das Team kann die Ergebnisse beider Strukturen vergleichen, um sicherzustellen, dass keine Datenunterschiede bestehen. Tritt eine Anomalie auf, kann die Anwendung ohne Datenverlust zur alten Struktur zurückwechseln.
Dieser Ansatz erfordert sorgfältige Codierung auf der Anwendungsseite. Die Anwendung muss beide Strukturen kennen und Schreibvorgänge in beide handhaben. Sie benötigt auch eine Logik, um zu entscheiden, aus welcher Struktur gelesen wird. Das ist nicht trivial, aber weitaus sicherer als der Versuch einer einzigen Big-Bang-Migration, die entweder perfekt funktioniert oder einen schwerwiegenden Vorfall verursacht.
Migration betrifft Daten, nicht nur das Schema
Ein häufiger Fehler ist es, die Datenbankmigration als reinen Schema-Vorgang zu betrachten. Bereits vorhandene Daten müssen nach der Migration konsistent bleiben. Jede Migration benötigt zwei Skripte: ein Vorwärtsskript und ein Rollback-Skript. Das Rollback-Skript ist nicht einfach die Umkehrung des Vorwärtsskripts. Es muss die Daten exakt in den Zustand vor der Migration zurückversetzen, einschließlich aller Daten, die während des Migrationsprozesses von der Anwendung geändert worden sein könnten.
Wenn eine Migration beispielsweise eine Spalte umbenennt und ihre Werte transformiert, muss das Rollback-Skript sowohl die Spaltenumbenennung als auch die Werttransformation rückgängig machen. Wenn die Anwendung während des Migrationsfensters neue Daten in die umbenannte Spalte geschrieben hat, muss das Rollback-Skript diese Daten korrekt behandeln, nicht einfach verwerfen.
Wo die Migration in der Pipeline platziert wird
In einer CI/CD-Pipeline sollte die Datenbankmigration eine separate Phase sein, die unabhängig vom Anwendungs-Deployment läuft. Die Pipeline sollte die Migration nicht gleichzeitig mit dem Deployment des neuen Codes ausführen. Stattdessen läuft die Migration zuerst. Nachdem die Migration als erfolgreich bestätigt wurde, wird die neue Anwendungsversion deployed. Schlägt die Migration fehl, stoppt die Pipeline und das Team wird benachrichtigt, bevor die Anwendung betroffen ist.
Diese Trennung ist entscheidend. Wenn Migration und Deployment zusammen stattfinden und etwas schiefgeht, ist schwer zu sagen, ob das Problem von der Schemaänderung oder dem neuen Code herrührt. Die sequenzielle Ausführung gibt eine klare Verantwortlichkeit für jeden Fehler.
Wann manuelle Freigabe erforderlich ist
Organisationen, die schon lange bestehen, übernehmen in der Regel eine einfache Regel: Migrationen, die Daten ändern (nicht nur das Schema), erfordern eine manuelle Freigabe. Reine Schema-Migrationen, die additiv sind, wie das Hinzufügen einer nullable Spalte, können automatisch laufen. Das liegt nicht daran, dass Automatisierung unzuverlässig ist. Es liegt daran, dass Datenänderungen Konsequenzen haben, die schwerer vorhersehbar sind als strukturelle Änderungen.
Eine neue nullable Spalte wird nichts kaputt machen. Aber eine Migration, die Millionen von Zeilen aktualisiert, Werte transformiert oder Tabellen zusammenführt, kann subtile Fehler einführen, die nur unter bestimmten Datenbedingungen auftreten. Eine manuelle Überprüfung vor solchen Migrationen ist ein Sicherheitsnetz, kein Engpass.
Praktische Checkliste für sichere Datenbankmigrationen
- Spalten zuerst als nullable oder mit Standardwerten hinzufügen, Constraints später setzen.
- Datentypen ändern, indem Sie eine neue Spalte hinzufügen, befüllen und die Lesevorgänge schrittweise umstellen.
- Bei komplexen Umstrukturierungen alte und neue Strukturen parallel laufen lassen und Ergebnisse vergleichen.
- Immer ein Rollback-Skript schreiben, das die Daten exakt in den Zustand vor der Migration zurückversetzt.
- Migrationen vor Anwendungs-Deployments ausführen, nicht gleichzeitig.
- Manuelle Freigabe für datenändernde Migrationen verlangen, additive Schema-Migrationen automatisch zulassen.
Das Fazit
Datenbankmigrationen müssen nicht beängstigend sein. Der Schlüssel liegt darin, jede Änderung in kleine, umkehrbare Schritte zu zerlegen. Hinzufügen, bevor Sie entfernen. Altes und Neues nebeneinander laufen lassen, bevor Sie umschalten. Und immer einen Rückweg haben, der nicht auf der Wiederherstellung aus einem Backup basiert. Wenn Sie jede Migration als eine Reihe von sicheren, testbaren Schritten behandeln, nehmen Sie die Angst und machen Datenbankänderungen zu einem normalen Teil Ihres Auslieferungsprozesses.