Zum Inhalt springen
DedicatedPHP Kontakt

Unterbrechungsfreie Datenbankmigrationen in PHP

Erfahren Sie, wie Sie PHP-Schemata mit temporärer Kompatibilität, fortsetzbaren Migrationen, Datenvalidierung und operativem Rollback weiterentwickeln.

Redaktionelles Diagramm der Phasen Erweitern, migrieren und entfernen zur Änderung eines Datenbankschemas in einer PHP-Anwendung

Eine Schemamigration kann fehlschlagen, obwohl die Codeänderung die Tests bestanden hat. In der Produktion ändert sich eine Anwendung normalerweise nicht auf einmal: Webprozesse, Queue-Worker, geplante Aufgaben und Replikate können gleichzeitig unterschiedliche Versionen ausführen. Wenn eine neue Version eine Spalte entfernt, die ein alter Worker noch liest, oder wenn eine Spalte verpflichtend wird, bevor alle schreibenden Komponenten sie befüllen, ist das Deployment nicht mehr kompatibel.

Unterbrechungsfreie Datenbankmigrationen in PHP behandeln Schema und Daten als Komponenten eines operativen Vertrags. Das Ziel besteht nicht nur darin, eine korrekte DDL-Anweisung auszuführen, sondern Lese- und Schreibzugriffe verfügbar zu halten, während alte und neue Versionen nebeneinander bestehen, und einen realistischen Wiederherstellungsweg beizubehalten.

Warum das Schema bereits getesteten Code brechen kann

Warum das Schema bereits getesteten Code brechen kann — guía visual de DedicatedPHP

Lokale Tests gehen häufig von einer von Grund auf neu erstellten oder sofort aktualisierten Datenbank aus. Dieses Szenario lässt den Übergang aus: unvollständige historische Daten, Millionen von Zeilen, Sperren, persistente Verbindungen und asynchrone Verbraucher. Eine scheinbar geringfügige Änderung kann Fehler oder Leistungseinbußen verursachen.

  • Das Umbenennen oder Entfernen einer Spalte bricht Abfragen, ORM-Mapper, Berichte und Prozesse, die noch den alten Namen verwenden.
  • Das Hinzufügen einer NOT NULL-Einschränkung schlägt fehl, wenn alte Zeilen keinen Wert enthalten oder eine schreibende Komponente das neue Feld noch nicht kennt.
  • Eine Typänderung kann Werte abschneiden, Vergleiche verändern, Indizes ungültig machen oder kostspielige Konvertierungen auslösen.
  • Das Erstellen eines Index oder das Umschreiben einer großen Tabelle kann Sperren halten und die Latenz normaler Vorgänge erhöhen.
  • Ein Massenupdate in einer einzigen Transaktion kann das Transaktionsprotokoll erschöpfen, um Ressourcen konkurrieren oder die Replikation erschweren.

Die relevante Frage lautet: Welche Codeversionen können während des gesamten Deployment-Fensters jede Darstellung eines Datenwerts lesen und schreiben? Die Antwort muss die ausführbaren Komponenten einschließen, die nicht automatisch neu gestartet werden, und nicht nur HTTP-Anfragen.

Temporäre Kompatibilität zwischen Code, Daten und Prozessen

Während eines schrittweisen Deployments gibt es mindestens drei Zustände, die kompatibel sein müssen: alter Code, neuer Code sowie Daten mit altem, neuem oder teilweise transformiertem Format. Kompatibilität bedeutet nicht zwingend, dass jeder Verbraucher alle Formate für immer versteht; sie besteht darin, ein begrenztes Zeitfenster zu definieren, in dem die vorhersehbaren Kombinationen funktionieren.

Um beispielsweise full_name durch first_name und last_name zu ersetzen, sollte das ursprüngliche Feld nicht zu Beginn gelöscht werden. Die neue Version kann beide Formate schreiben und zuerst die neuen Felder lesen, wenn sie vollständig sind, mit einem expliziten Fallback auf den alten Wert. Die vorherige Version arbeitet weiterhin mit full_name. Sobald die historischen Daten transformiert und die alten Verbraucher entfernt wurden, kann das Lesen ausschließlich von der neuen Struktur abhängen.

Vermeiden Sie, dass die temporäre Kompatibilität über Controller verstreut wird. Zentralisieren Sie Lesen, Schreiben und Normalisierung in einem Domain-Service oder Repository. So lässt sich prüfen, welche Version des Formats erzeugt wird, welcher Wert Vorrang hat und wann die Übergangslogik entfernt werden kann. Eine Migrationsvorlage ersetzt dieses Kompatibilitätsmodell nicht: Die Vorlage führt Änderungen aus; das Modell definiert, wie sich die Anwendung während des Übergangs verhält.

Das Muster „Erweitern, migrieren und entfernen“

1. Erweitern, ohne aktuelle Verbraucher ungültig zu machen

Die erste Phase fügt Fähigkeiten hinzu, ohne bestehende zu entfernen: eine nullable Spalte, eine neue Tabelle, einen zusätzlichen Index oder eine parallele Struktur. Destruktive Änderungen müssen vermieden werden und, wenn die Datenbank-Engine es erfordert, sollte die Erstellungsmethode geplant werden, um Sperren zu reduzieren. Das Hinzufügen einer Spalte bedeutet nicht, dass es sicher ist, sofort einen Standardwert festzulegen, alle Zeilen neu zu berechnen oder sie als verpflichtend zu deklarieren.

Prüfen Sie vor der Ausführung des Vorgangs die Tabellengröße, die häufigsten Abfragen, Fremdschlüssel, den verfügbaren Speicherplatz, die Replikationslast und das spezifische Verhalten der Datenbank-Engine. Testen Sie an einer repräsentativen Kopie oder in einer Umgebung mit vergleichbarem Volumen und vergleichbarer Parallelität. Definieren Sie außerdem beobachtbare Grenzen: Dauer, zulässige Latenz, Fehlerrate und Abbruchbedingung.

2. Kompatiblen Code mit schreibenden und lesenden Komponenten bereitstellen

Anschließend wird Code bereitgestellt, der beide Darstellungen versteht. Neue schreibende Komponenten können dual schreiben, wenn Kosten und Konsistenz dies zulassen. Lesende Komponenten müssen eine eindeutige Priorität festlegen: den neuen Wert lesen, wenn er validiert ist; andernfalls den alten verwenden. Verwenden Sie keine Ausnahme als Fallback-Mechanismus, da sie Datenfehler verschleiert und unnötige Arbeit zum kritischen Pfad hinzufügt.

Das duale Schreiben erfordert explizite Entscheidungen. Wenn ein Update beide Strukturen betrifft, bestimmen Sie, ob es in derselben Transaktion erfolgen muss. Falls das nicht möglich ist, entwerfen Sie eine idempotente Abgleichslogik und Metriken zur Erkennung von Abweichungen. Auch Events, Caches, APIs und Exporte sind Verbraucher: Nur das PHP-Repository zu ändern, garantiert keine Ende-zu-Ende-Kompatibilität.

3. Die historischen Daten fortsetzbar migrieren

Nachdem der kompatible Code bereitgestellt wurde, transformieren Sie die bestehenden Datensätze in kleinen Batches. Jeder Batch muss wiederholbar sein, ohne Effekte zu duplizieren oder Daten zu beschädigen. Verwenden Sie einen stabilen Schlüssel oder einen persistenten Cursor, Größenbegrenzungen, Fortschrittsprotokollierung und kontrollierte Wiederholungsversuche. Vermeiden Sie eine Offset-basierte Paginierung über sich ändernde Mengen, da sie Zeilen überspringen oder erneut verarbeiten kann.

$lastId = 0; // Für einen positiven und aufsteigenden Primärschlüssel.

while (true) {
    $rows = $repository->findPendingAfterId($lastId, 500);

    if ($rows === []) {
        break;
    }

    foreach ($rows as $row) {
        $repository->migrateIfNeeded($row);
        $lastId = $row->id;
    }
}

Dieses Muster erfordert, dass findPendingAfterId() Zeilen zurückgibt, die aufsteigend nach demselben als Cursor verwendeten Schlüssel sortiert sind. Der Cursor beginnt mit einem Wert vor dem ersten gültigen Bezeichner und wird erst nach der Verarbeitung jeder Zeile weitergesetzt; die Beendigung hängt davon ab, dass die Abfrage keinen Batch zurückgibt. Bei einer fortgesetzten Ausführung muss der bestätigte Wert von $lastId persistiert werden. migrateIfNeeded() muss den aktuellen Zustand prüfen und dasselbe Ergebnis erzeugen, wenn es erneut ausgeführt wird.

Messen Sie ausstehende Zeilen, transformierte Zeilen, Validierungsfehler und Unterschiede zwischen Formaten. Erklären Sie die Phase nicht für abgeschlossen, nur weil die Tabelle durchlaufen wurde: Prüfen Sie auch referenzielle Integrität, Eindeutigkeit, fachliche Summen und Stichproben kritischer Datensätze.

4. Lesezugriffe umstellen, beobachten und entfernen

Wenn die historischen Daten vollständig migriert sind und die alten Prozesse nicht mehr ausgeführt werden, stellen Sie die Lesezugriffe so um, dass sie ausschließlich die neue Struktur verwenden. Diese Aktivierung kann über eine kontrollierte Konfiguration schrittweise erfolgen, darf aber nicht mit dem Deployment verwechselt werden: Das Deployment macht Code verfügbar; die Aktivierung verändert, welchen Pfad der Traffic nutzt.

Beobachten Sie Abfragefehler, unerwartete Nullwerte in Feldern, funktionale Abweichungen, Antwortzeiten und den Zustand der Worker. Entfernen Sie das duale Schreiben, Übergangsabhängigkeiten und schließlich die alte Spalte, den Index oder die Tabelle erst nach einem definierten Beobachtungsfenster. Veraltete Strukturen unbegrenzt beizubehalten, erhöht Unklarheit und Kosten; sie zu früh zu entfernen, beseitigt die einfache Wiederherstellung.

Nullwerte, Typen, Einschränkungen und Indizes ohne Betriebsunterbrechung

Eine neue Spalte beginnt häufig als nullable, weil historische Datensätze sie noch nicht besitzen. Die Anwendung muss das Fehlen als erwarteten Zustand behandeln, nicht als unmöglichen Fall. Nach Abschluss und Validierung des Backfills kann eine Einschränkung durchgesetzt werden, sofern alle aktiven schreibenden Komponenten einen gültigen Wert bereitstellen.

Erstellen Sie bei Typänderungen eine neue Spalte und konvertieren Sie die Werte explizit. Dies ermöglicht, nicht konvertierbare Werte zu erkennen, Rundungs- oder Normalisierungsregeln anzuwenden und beide Ergebnisse zu vergleichen, bevor die vorherige Spalte ersetzt wird. Den Typ direkt zu ändern, kann in begrenzten Fällen angemessen sein, muss jedoch durch das Verhalten der Engine, das Volumen und die Kompatibilität der Abfragen gerechtfertigt sein.

Indizes erfordern eine gleichwertige Analyse. Ein neuer Index kann Lesezugriffe verbessern, doch seine Erstellung verbraucht Ressourcen, und eine ungeeignete Erstellungsstrategie kann schreibende Komponenten blockieren. Validieren Sie den Ausführungsplan der Abfrage, die ihn benötigt; fügen Sie Indizes nicht nach Intuition hinzu. Wenn die Engine Erstellungsmethoden mit geringerer Sperrwirkung bietet, verstehen Sie deren Anforderungen und Einschränkungen, bevor Sie sie in den Plan aufnehmen.

Rollback: Eine Code-Reversion bedeutet nicht immer eine Daten-Reversion

Ein operativer Rollback muss in getrennte Entscheidungen aufgeteilt werden. Solange die alte Struktur und das duale Schreiben bestehen, ist es in der Regel möglich, zum vorherigen Code zurückzukehren. Wenn das neue Format jedoch Informationen akzeptiert hat, die das alte Modell nicht darstellen kann, stellt das Rückgängigmachen des Schemas diese Daten nicht semantisch wieder her.

  • Reversibel: einen neuen Lesezugriff deaktivieren und zum Fallback zurückkehren, während beide Strukturen erhalten bleiben.
  • Kompensierbar: Daten aus einer definierten Quelle korrigieren oder rekonstruieren, mit einem auditierten Prozess.
  • Irreversibel: eine Struktur löschen oder Transformationen akzeptieren, die Präzision verlieren, ohne das Original zu bewahren.

Dokumentieren Sie den Punkt, ab dem keine Rückkehr mehr möglich ist, die für seine Autorisierung verantwortliche Person, die notwendigen Sicherungen oder Exporte und das Verfahren zum Anhalten von Workern. Eine down()-Methode in einem Migrationstool ist für sich allein kein Rollback-Plan: Sie kann DDL zurücksetzen, garantiert aber nicht die Gültigkeit der während des Übergangs geschriebenen Daten.

Tests, Nachweise und Checkliste

Tests, Nachweise und Checkliste — guía visual de DedicatedPHP

Testen Sie eine Kompatibilitätsmatrix: alter Code mit erweitertem Schema, neuer Code mit noch nicht migrierten Daten, neuer Code mit transformierten Daten sowie asynchrone Prozesse in gemischten Versionen. Schließen Sie unterbrochene und fortgesetzte Migrationen, ungültige Datensätze, Schreibparallelität und gegebenenfalls die Wiederherstellung einer früheren Version ein.

  • Betroffene Tabellen, Abfragen, Worker, Integrationen und Berichte inventarisieren.
  • Den temporären Lese- und Schreibvertrag einschließlich Nullwerten und Prioritäten definieren.
  • Erweiterung, kompatibles Deployment, Backfill, Aktivierung und Entfernung in unabhängige Schritte trennen.
  • Die Auswirkungen von DDL, Indizes und Batches mit repräsentativen Daten schätzen.
  • Den Datenprozess idempotent, fortsetzbar und messbar gestalten.
  • Integritätsvalidierungen und nachgelagerte Beobachtungsschwellen festlegen.
  • Rollback, Kompensationen und den Punkt, ab dem keine Rückkehr mehr möglich ist, dokumentieren.
  • Die alte Kompatibilität und Struktur nur mit Nachweisen entfernen, dass keine Verbraucher mehr vorhanden sind.

Mit Disziplin angewendet, verwandelt dieses Muster eine Datenbankänderung mit hohem Risiko in eine überprüfbare Abfolge. Entscheidend ist, das Nebeneinanderbestehen als Teil des Produkts und des Betriebs zu gestalten, nicht als verborgenes Detail innerhalb einer Migration.

Möchten Sie diese Ideen in Ihrem Projekt anwenden?Lass uns über deine PHP-Plattform sprechen.
Verwandten Dienst anzeigen