Wenn das Datenbankschema stimmt, aber die Daten falsch sind
Du hast gerade eine Datenbankmigration ausgeführt, die eine neue Spalte hinzugefügt hat. Alles sah gut aus. Die Schemaänderung war erfolgreich, die Spalte existiert, und die Anwendung läuft. Aber dann fällt es jemandem auf: Alle Benutzer, die sich vor drei Jahren registriert haben, sollten als verifiziert markiert sein – sind sie aber nicht. Oder die Telefonnummern, die du migriert hast, haben jetzt inkonsistente Formate, weil die alten Daten nicht den neuen Regeln folgten.
Das Schema ist korrekt. Der Spaltentyp stimmt. Die Constraints sind gültig. Das Problem sind die Daten selbst.
Diese Situation ist schlimmer als eine fehlgeschlagene Migration. Eine fehlgeschlagene Migration ist offensichtlich. Du siehst den Fehler, du weißt, dass etwas kaputt ist, und du kannst handeln. Aber eine Migration, die mit schlechten Daten erfolgreich ist, ist subtil. Sie kann stunden- oder tagelang in der Produktion laufen, bevor es jemand bemerkt. Und wenn du es bemerkst, ist der natürliche Impuls, in Panik zu geraten und nach Möglichkeiten zu suchen, alles rückgängig zu machen.
Aber das Zurücksetzen des Schemas, um die Daten zu reparieren, schafft eine neue Reihe von Problemen. Die Anwendung funktioniert möglicherweise nicht mehr mit dem alten Schema. Du könntest Daten verlieren, die bereits korrekt waren. Und du machst eine strukturelle Änderung rückgängig, die eigentlich richtig war, nur um Inhalte zu korrigieren, die falsch waren.
Das eigentliche Problem ist nicht das Schema
Wenn eine Migration eine Spalte wie is_verified mit einem Standardwert von false hinzufügt, ist die Schemaänderung unkompliziert. Die Spalte existiert, der Standardwert funktioniert, und neue Datensätze verhalten sich korrekt. Das Problem ist, dass bestehende Benutzer, die verifiziert sein sollten, jetzt als nicht verifiziert markiert sind. Das Schema hat das nicht verursacht. Die Migrationslogik hat das nicht verursacht. Die Lücke lag im Verständnis dessen, was die vorhandenen Daten hätten sein sollen.
Ein weiteres häufiges Beispiel: Eine Migration ändert die Speicherung von Telefonnummern, sodass Ländervorwahlen erforderlich sind. Das neue Format ist korrekt, und neue Einträge folgen den Regeln. Aber die alten Telefonnummern, die ohne Ländervorwahl gespeichert wurden, sind jetzt inkonsistent. Das Schema ist in Ordnung. Die Daten sind es nicht.
In beiden Fällen ist die Lösung nicht, das Schema zurückzusetzen. Die Lösung ist, die Daten zu reparieren, während das Schema intakt bleibt.
Compensating Scripts: Daten reparieren, ohne die Struktur anzutasten
Ein Compensating Script ist eine Migration, die nur Daten ändert, nicht das Schema. Es läuft wie eine normale Migration, durchläuft dieselbe Pipeline und folgt demselben Bereitstellungsprozess. Aber anstelle von ALTER TABLE, CREATE INDEX oder ADD COLUMN enthält es nur UPDATE-, INSERT- oder DELETE-Anweisungen, die die Daten korrigieren.
Das Ziel ist einfach: Die Daten in den korrekten Zustand versetzen, ohne die Tabellenstruktur zu ändern.
Hier ist ein praktisches Beispiel. Nach dem Hinzufügen einer currency-Spalte mit einem Standardwert von 'IDR' stellt das Team fest, dass alle Transaktionen von internationalen Partnern 'USD' verwenden sollten. Das Compensating Script sieht so aus:
UPDATE transactions SET currency = 'USD' WHERE partner_type = 'international';
Keine Schemaänderungen. Keine neuen Spalten. Keine Typkonvertierungen. Nur eine gezielte Datenkorrektur.
Compensating Scripts behandeln auch partielle Migrationsfehler. Stell dir eine Migration vor, die eine neue Tabelle erstellt und Daten aus einer alten verschiebt. Einige Zeilen können aufgrund einer Constraint-Verletzung nicht übertragen werden. Anstatt die gesamte Migration zurückzusetzen und von vorne zu beginnen, kann ein Compensating Script die verbleibenden Zeilen verarbeiten. Es fügt nur die Datensätze ein oder aktualisiert sie, die übersehen wurden, ohne die vollständige Migration erneut auszuführen.
Compensating Scripts idempotent machen
Es gibt eine Regel, die wichtiger ist als jede andere: Compensating Scripts müssen idempotent sein. Das zweimalige Ausführen des Skripts sollte dasselbe Ergebnis liefern wie das einmalige Ausführen.
Das ist keine theoretische Überlegung. In der Praxis werden Migrationen erneut ausgeführt. Eine Pipeline startet neu. Eine Umgebung wird aufgefrischt. Jemand führt die Migration manuell während des Debuggens aus. Wenn dein Skript nicht idempotent ist, kann das zweimalige Ausführen deine Daten beschädigen.
Die Lösung ist einfach. Überprüfe immer den aktuellen Zustand, bevor du Änderungen vornimmst. Verwende eine WHERE-Klausel, die spezifisch genug ist, um nur die Zeilen zu betreffen, die korrigiert werden müssen. Wenn deine Datenbank es unterstützt, verwende ON CONFLICT-Klauseln für Einfügungen.
Anstelle von:
UPDATE transactions SET currency = 'USD' WHERE partner_type = 'international';
Schreibe dies:
UPDATE transactions SET currency = 'USD' WHERE partner_type = 'international' AND currency IS DISTINCT FROM 'USD';
Der Unterschied ist klein, aber entscheidend. Die zweite Version aktualisiert nur Zeilen, in denen die Währung nicht bereits 'USD' ist. Wenn du es hundertmal ausführst, werden nur die Zeilen betroffen, die geändert werden müssen, und das auch nur beim ersten Durchlauf.
Wann Compensating Scripts nicht ausreichen
Compensating Scripts sind keine universelle Lösung. Sie funktionieren, wenn das Schema korrekt ist und nur die Daten repariert werden müssen. Wenn das Schema selbst falsch ist, brauchst du immer noch eine ordentliche Schema-Migration.
Wenn du zum Beispiel eine Spalte mit dem falschen Datentyp hinzugefügt hast oder wenn die Spalten-Constraints zu streng für die zu speichernden Daten sind, kann ein Compensating Script nicht helfen. Du musst das Schema ändern. Wenn eine Migration einen Bug eingeführt hat, der Daten in einer Weise beschädigt hat, die nicht mit einfachen UPDATE-Anweisungen korrigiert werden kann, brauchst du möglicherweise einen komplexeren Ansatz.
Aber für den häufigen Fall, dass das Schema richtig und die Daten falsch sind, sind Compensating Scripts sicherer als jede Alternative. Sie vermeiden die Risiken von Down-Migrationen, sie erfordern keine Wiederherstellung aus Backups, und sie können ausgeführt werden, während die Anwendung noch Traffic bedient.
Eine kurze Checkliste für das Schreiben von Compensating Scripts
- Bestätige, dass das Schema korrekt ist, bevor du das Skript schreibst. Wenn sich die Struktur ändern muss, kümmere dich zuerst darum.
- Schreibe das Skript als neue Migration, nicht als Hotfix, der direkt auf die Datenbank angewendet wird.
- Mache jede Anweisung idempotent. Überprüfe Bedingungen, bevor du aktualisierst oder einfügst.
- Teste das Skript gegen eine Kopie der Produktionsdaten, nicht nur gegen eine leere Datenbank.
- Füge Logging oder Kommentare hinzu, die erklären, warum die Datenkorrektur notwendig ist, damit zukünftige Teammitglieder den Kontext verstehen.
Das Fazit
Wenn eine Migration schiefgeht, ist der Instinkt oft, alles rückgängig zu machen. Aber das Rückgängigmachen des Schemas ist eine schwere Operation, die die Anwendung beschädigen und korrekte Daten verlieren kann. Compensating Scripts geben dir ein leichteres, präziseres Werkzeug. Sie erlauben dir, die Daten zu reparieren, während das funktionierende Schema erhalten bleibt. Wenn du das nächste Mal eine Migration siehst, die erfolgreich war, aber schlechte Daten hinterlassen hat, frage dich: Ist das Schema falsch oder sind die Daten falsch? Wenn die Antwort die Daten sind, schreibe ein Compensating Script. Es ist schneller, sicherer und weniger störend als ein Rollback.