Der gefährlichste Deploy in fast jedem Projekt ist nicht der mit dem neuen Feature, sondern der mit der Schema-Änderung. Code kannst du zurückrollen, Daten nicht — und eine Migration, die auf einer großen Tabelle eine Sperre hält, legt deine Anwendung still, während das Deployment-Skript scheinbar fröhlich weiterläuft.
Das gute daran: Es gibt ein Muster, das das Problem in fast allen Fällen auflöst, und es ist weder neu noch kompliziert. Es kostet nur einen Deploy mehr, als man gern hätte.
Warum “Spalte umbenennen” ein Ausfall ist
Der Kern des Problems ist selten die Datenbank, sondern die Gleichzeitigkeit. Während du deployst, laufen für einen Moment alte und neue Version der Anwendung nebeneinander — sei es, weil mehrere Instanzen nacheinander getauscht werden, sei es, weil eine Anfrage noch in Bearbeitung ist, während das Schema sich bereits ändert.
Wenn du eine Spalte in einem Schritt umbenennst, ist die alte Version, die noch den alten Namen abfragt, sofort kaputt. Wenn du eine Spalte löschst, gilt dasselbe. Und wenn du eine neue Pflichtspalte ohne Vorgabewert hinzufügst, brechen alle Schreibvorgänge der alten Version, die diese Spalte nicht kennt.
Anders gesagt: Eine Migration muss nicht nur mit dem neuen Code funktionieren, sondern gleichzeitig mit dem alten. Sobald man das als Anforderung akzeptiert, folgt der Rest fast von selbst.
Expand, Migrate, Contract
Das Muster besteht aus drei Phasen, und die Disziplin liegt darin, sie tatsächlich getrennt auszuliefern.
Expand — additiv erweitern. Du fügst das Neue hinzu, ohne das Alte anzufassen. Neue Spalte, neue Tabelle, neuer Index. Alles, was du in dieser Phase tust, ist für die alte Anwendungsversion unsichtbar und damit ungefährlich. Neue Spalten sind in dieser Phase immer optional und haben immer einen Vorgabewert.
Migrate — beide Wege bedienen. Jetzt kommt der Code, der neu schreibt, aber alt noch lesen kann. Für eine Weile werden Daten in beide Strukturen geschrieben, und die vorhandenen Altdaten werden im Hintergrund in Häppchen umkopiert — nicht in einem einzigen großen Statement, das minutenlang Sperren hält, sondern portionsweise mit Pausen dazwischen. Erst wenn diese Hintergrundarbeit vollständig durchgelaufen ist und du geprüft hast, dass beide Seiten übereinstimmen, geht es weiter.
Contract — zurückbauen. Der Code liest nur noch die neue Struktur, dann fällt die alte weg. Diese Phase kommt in einem eigenen Deploy, gern Tage später. Und genau hier hört die Disziplin bei den meisten Teams auf: Die Aufräumphase wird verschoben und dann vergessen, und die Datenbank füllt sich über die Jahre mit Halb-Migrationen. Setz dir die dritte Phase als Aufgabe ein, wenn du die erste ausführst.
Der praktische Effekt: Zwischen jedem Schritt ist das System in einem Zustand, in dem sowohl alte als auch neue Anwendungsversion laufen können. Damit ist jeder einzelne Deploy zurückrollbar.
Die Fallen, die im Testsystem nicht auffallen
Fast alle Migrations-Unfälle haben gemeinsam, dass sie auf einer Tabelle mit tausend Zeilen problemlos liefen und auf einer mit Millionen nicht.
- Sperren. Viele Schemaänderungen brauchen eine exklusive Sperre auf der Tabelle. Selbst wenn die Änderung an sich schnell ist, muss sie erst diese Sperre bekommen — und wenn eine lange laufende Abfrage im Weg ist, stellt sich die Migration in die Warteschlange und blockiert dabei alles, was danach kommt. Ein knapp gesetztes Sperren-Zeitlimit ist deshalb wichtiger als das Zeitlimit für die Migration selbst: Lieber scheitert die Migration schnell, als dass sie die Anwendung mitzieht.
- Indizes. Einen Index anzulegen kann bei großen Tabellen sehr lange dauern. Die meisten ernstzunehmenden Datenbanken bieten dafür einen Modus, der ohne Schreibsperre arbeitet — deutlich langsamer, aber ohne Ausfall. Der hat eigene Regeln, etwa dass er nicht innerhalb einer Transaktion laufen darf, was viele Migrations-Werkzeuge standardmäßig tun.
- Fremdschlüssel und Constraints. Eine neue Einschränkung prüft beim Anlegen den gesamten Bestand. Der übliche Weg ist, sie zunächst ohne Prüfung anzulegen und die Validierung als separaten, schonenderen Schritt nachzuziehen.
- Vorgabewerte. Bei älteren Datenbankversionen bedeutet eine neue Spalte mit Vorgabewert ein komplettes Neuschreiben der Tabelle. Neuere Versionen können das ohne, aber du solltest wissen, welche Version tatsächlich in Produktion läuft — nicht welche in der Doku steht.
Migrationen gehören getestet, nicht gehofft
Die Mindestanforderung ist eine Kopie der Produktionsdaten in realistischer Größe, gegen die die Migration einmal komplett läuft, mit Zeitmessung. Wenn eine Migration dort acht Minuten braucht, weißt du, dass sie in Produktion nicht “kurz eben” durchläuft.
Zweiter Punkt, den viele auslassen: Teste den Rückweg. Nicht jede Migration braucht eine automatische Rückwärts-Variante — bei destruktiven Schritten ist die ehrliche Antwort oft, dass es keinen gibt. Aber dann muss das bewusst so entschieden und aufgeschrieben sein, statt eine Rückwärts-Funktion vorzutäuschen, die im Ernstfall Daten verliert.
Dritter Punkt: Trenne Migrationen vom Anwendungs-Deploy. Wenn dein Deployment-Skript Migration und Code in einem Rutsch macht, kannst du den Code nicht mehr allein zurückrollen. Wenn die Migration ein eigener, bewusst ausgelöster Schritt ist, behältst du in jeder Phase die Kontrolle — und du kannst sie zu einer Zeit fahren, in der wenig los ist.
Das Muster gilt auch außerhalb der Datenbank
Wenn du einmal in diesem Denken drin bist, siehst du dasselbe Muster überall, wo zwei Versionen kurzzeitig koexistieren: bei API-Feldern, bei Nachrichtenformaten in Warteschlangen, bei Konfigurationsschlüsseln, bei zwischengespeicherten Objekten, deren Struktur sich ändert. Immer gilt dieselbe Reihenfolge: erst additiv erweitern, dann beide Wege bedienen, dann das Alte entfernen. Nie in einem Schritt.
Es fühlt sich umständlich an, drei Deploys für eine Umbenennung zu machen. Es dauert aber immer noch weniger lang als der eine Abend, an dem der eine schnelle Weg nicht funktioniert hat.
Wenn du eine Migration vor dir hast, bei der du dir unsicher bist, ob sie sperrt: Im Discord der Community schaut regelmäßig jemand mit drauf, bevor es live geht.