Webentwicklung

Datenbankschemamigrationen für kleine PHP-Anwendungen - Strukturen ohne Rätselraten ändern

Datenbankschemamigrationen für kleine PHP-Anwendungen - Strukturen ohne Rätselraten ändern

Ein Anwendungsrelease lasst sich durch das Auschecken eines fruheren Commits zurucksetzen. Eine Datenbank ist weniger kooperativ: Sie enthalt Zustand, der sich wahrend des Betriebs angesammelt hat. Wenn ein Deployment eine Spalte hinzufugt, Tausende Werte umwandelt und danach die alte Spalte entfernt, stellt die Ruckkehr zum fruheren Code die verworfenen Daten nicht wieder her. Die praktische Frage lautet deshalb nicht nur: "Wie fuhre ich ALTER TABLE aus?", sondern: "Wie wird diese Anderung wiederholbar, sichtbar und wiederherstellbar?"

Datenbankschemamigrationen bieten eine Antwort, aber keine Magie. Sie machen strukturelle Anderungen zu geordneten Artefakten, die gepruft und getestet werden konnen. Sie machen nicht jede SQL-Anweisung transaktional, beseitigen keine Deployment-Sperren und garantieren nicht, dass verlorene Daten wiederhergestellt werden konnen. Fur eine kleine PHP-Anwendung ist diese Grenze wichtiger als ein grosses Werkzeug, dessen Aufgabe unklar bleibt.

Eine Migration ist ein aufgezeichneter Ubergang, kein Snapshot

Eine Schemamigration beschreibt den Weg von einem bekannten Datenbankzustand zum nachsten. Sie kann eine Tabelle erstellen, einen Index hinzufugen, eine nullable Spalte einfuhren oder bestehende Zeilen auffullen. Die Dokumentation von Doctrine Migrations beschreibt dies als Versionierung des Datenbankschemas, damit Anderungen vor dem Produktionseinsatz gepruft und getestet werden konnen.

Das unterscheidet sich von einem reinen Schema-Dump fur Neuinstallationen. Ein Dump kann den heutigen Sollzustand beschreiben, erklart aber nicht, wie eine bestehende Installation von der Struktur des letzten Monats zur heutigen gelangt. Eine Migration bewahrt diesen Weg.

Sie unterscheidet sich auch von einer normalen Anwendungstransaktion. Eine Transaktion gruppiert Laufzeitschreibvorgange, etwa das Erstellen einer Bestellung und ihrer Positionen. Eine Schemamigration verandert die Strukturen, von denen diese Schreibvorgange abhangen. Beide Konzepte beruhren sich bei Datenumwandlungen, losen aber unterschiedliche Koordinationsprobleme.

Das kleinste verlassliche System besteht aus zwei Teilen

Der erste Teil ist ein Verzeichnis mit Migrationsdateien, das zusammen mit dem Anwendungscode gespeichert wird. Jede Datei erhalt eine eindeutige Version und eine klare Beschreibung, zum Beispiel:

migrations/
  202608310001_add_summary_to_posts.sql
  202608310002_backfill_post_summaries.sql
  202608310003_require_post_summary.sql

Das genaue Namensformat ist weniger wichtig als eine stabile Identitat und deterministische Reihenfolge. Die Skripte gehoren in die Versionskontrolle, weil ein Release sowohl den Code als auch die erwarteten Datenbankanderungen enthalten sollte. Die Flyway-Dokumentation von Redgate beschreibt Migrationen als inkrementelle Schema- oder Datenanderungen, die in verschiedenen Umgebungen in derselben Reihenfolge ausgefuhrt werden.

Der zweite Teil ist ein Migrationsledger in der Datenbank. Ein minimales Ledger kann Version, Beschreibung, Prufsumme, Ausfuhrungszeit und Erfolgsstatus speichern. Vor einer Anderung vergleicht der Runner die vorhandenen Dateien mit den aufgezeichneten Versionen. Er fuhrt nur ausstehende Migrationen der Reihe nach aus und protokolliert das Ergebnis.

Dieses Ledger beantwortet eine Frage, die ein Verzeichnis allein nicht beantworten kann: Was hat genau diese Datenbank bereits erhalten? Entwicklung, Staging und Produktion konnen dasselbe Repository verwenden und trotzdem unterschiedliche Migrationsstande haben. Die History-Tabelle macht diese Differenz sichtbar.

Bereits ausgerollte Geschichte nicht beilaufig umschreiben

Wird eine Migration nach ihrer Ausfuhrung in einer gemeinsam genutzten oder produktiven Umgebung still geandert, entstehen zwei Bedeutungen fur dieselbe Version. Eine neue Datenbank erhalt das uberarbeitete SQL, eine bestehende behalt die Wirkung des ursprunglichen SQL. Prufsummen erkennen die Abweichung, entscheiden aber nicht, welcher Zustand beabsichtigt war.

Der sicherere Standard ist eine zusatzliche Korrekturmigration. Das wirkt weniger ordentlich, doch die Abfolge zeigt ehrlich, was geschehen ist. Historische Migrationen konnen spater bewusst konsolidiert werden; das ist jedoch eine eigene Wartungsoperation mit definierter Baseline, keine unsichtbare Bearbeitung.

Kompatibilitat als Deployment-Eigenschaft behandeln

Angenommen, eine PHP-Anwendung soll fur bestehende Beitrage ein verpflichtendes Feld summary erhalten. Eine einzige Migration, die eine NOT NULL-Spalte hinzufugt, jede Zeile umschreibt und sofort Code ausrollt, der sie voraussetzt, bundelt mehrere Risiken in einem Moment.

Eine gestufte Anderung ist leichter zu beurteilen:

  1. summary zunachst nullable hinzufugen, damit alter Code gultig bleibt.
  2. Code ausrollen, der Zusammenfassungen schreibt, alte Zeilen aber weiterhin toleriert.
  3. Bestehende Zeilen in kontrollierten Batches auffullen und das Ergebnis prufen.
  4. Lesezugriffe erst nach der Validierung auf das neue Feld umstellen.
  5. Die Pflichtbedingung in einer spateren Migration hinzufugen.

Das folgende SQL ist bewusst nur eine Illustration und kein produktionsreifes Rezept:

-- Expand: old code can ignore this nullable column.
ALTER TABLE posts ADD COLUMN summary VARCHAR(500) NULL;

-- A separate, reviewed data migration fills existing rows.
UPDATE posts
SET summary = LEFT(content, 500)
WHERE summary IS NULL;

-- Contract only after the application and data are ready.
ALTER TABLE posts MODIFY summary VARCHAR(500) NOT NULL;

Echte Inhalte lassen sich nicht unbedingt durch das Abschneiden von HTML zusammenfassen. Das UPDATE ist daher keine Empfehlung fur ein Publikationssystem. Es macht vielmehr die Frage sichtbar, die ein Migrationsreview beantworten muss: Bewahrt die Umwandlung die Bedeutung der alten Daten? Manchmal gibt es keine ehrliche automatische Zuordnung; dann muss das Design fehlende Werte oder eine manuelle Prufung zulassen.

Expand and contract trennt schwer umkehrbare Entscheidungen

Die allgemeinere Form dieses gestuften Vorgehens heisst haufig expand and contract. Wahrend der Expansion unterstutzt die Datenbank alte und neue Strukturen. Der Anwendungscode kann in beide schreiben, bestehende Daten werden aufgefullt und der neue Leseweg wird getestet. Bei der Kontraktion wird der alte Weg erst entfernt, wenn er nicht mehr benotigt wird.

Der Prisma Data Guide beschreibt die Abfolge so: neues Schema neben dem alten aufbauen, Clients anpassen, Daten migrieren, testen, Lesezugriffe umstellen, alte Schreibzugriffe beenden und zuletzt die ursprungliche Struktur entfernen. Pramod Sadalage und Martin Fowler beschreiben eine verwandte Ubergangsphase im evolutionary database design, in der alte und neue Zugriffsmuster vorubergehend nebeneinander bestehen.

Das Muster hilft, wenn wahrend eines Deployments mehrere Anwendungsprozesse unterschiedliche Releases ausfuhren oder ein Backfill nicht sicher in einem Wartungsfenster endet. Der Preis ist vorubergehende Komplexitat: doppelte Spalten, duale Schreibvorgange, Kompatibilitatszweige und Aufraumarbeiten. Eine kleine Website mit akzeptablem Wartungsfenster kann vernunftigerweise eine kurzere Offline-Migration wahlen. "Zero downtime" sollte kein Schlagwort sein, das ein fragileres Verfahren verdeckt.

Rollback ist keine universelle Undo-Taste

Migrationswerkzeuge konnen Gegenoperationen definieren, doch inverses SQL ist nicht dasselbe wie Wiederherstellung. Eine neue leere Spalte zu entfernen kann einfach sein. Eine geloschte Spalte mitsamt allen fruheren Werten wiederherzustellen ist es nicht. Das Aufteilen eines mehrdeutigen Feldes kann Informationen verlieren, die sich nicht verlasslich zusammensetzen lassen. Neue Schreibvorgange nach dem Cutover haben moglicherweise keine Darstellung im alten Schema.

Auch Datenbank-Engines haben eigene Transaktionsregeln. MariaDB dokumentiert, dass viele DDL-Anweisungen einschliesslich ALTER TABLE einen impliziten Commit auslosen. Das Einschliessen in START TRANSACTION schafft keine universelle Rollback-Grenze. Andere Engines und Operationen verhalten sich anders; deshalb muss die Dokumentation der verwendeten Version gepruft werden, statt von Anwendungstransaktionen darauf zu schliessen.

Ein realistischer Wiederherstellungsplan kann mehrere Optionen kombinieren:

  • Die Anwendung mit einer Korrekturmigration vorwarts reparieren.
  • Alte Strukturen vorubergehend behalten, damit Code zuruckgeschaltet werden kann.
  • Ein getestetes Backup wiederherstellen, wenn Daten zerstort oder beschadigt wurden.
  • Das Deployment anhalten und untersuchen, statt automatisch fortzufahren.

Ein Backup ist nur potenzielle Wiederherstellung, bis sein Restore-Weg getestet wurde. Akzeptabler Recovery Point und Recovery Time hangen von der Anwendung ab; eine personliche Website und ein Zahlungssystem sollten nicht dasselbe Risikomodell vortauschen.

Ein sorgfaltiger Ablauf fur ein kleines Team

1. Ausgangszustand definieren

Die erwartete Schemaversion festhalten. Enthalt die Produktion manuelle Anderungen ausserhalb der Versionskontrolle, muss dieser Drift vor weiterer Automatisierung geklart werden. Ein Runner kann von einer fiktiven Baseline nicht sicher ausgehen.

2. Jede Anderung eng begrenzen

Additive Schemaarbeit, Daten-Backfills, Code-Cutover und destruktives Aufraumen sollten getrennt werden, wenn ihre Fehlermodi verschieden sind. Kleinere Schritte sind leichter zu prufen und zu diagnostizieren; ohne zusammenhangenden Deployment-Plan verschieben zu winzige Schritte die Komplexitat jedoch nur.

3. Den Weg testen, nicht nur das Endschema

Eine Datenbank auf dem vorherigen Release anlegen, reprasentative Grenzfalle einfugen, ausstehende Migrationen anwenden und danach Anwendungstests ausfuhren. Eine Neuinstallation beweist, dass das heutige Schema erstellt werden kann; sie beweist nicht, dass alte Daten es sicher erreichen.

4. Datenumwandlungen ausdrucklich prufen

Zu klaren ist, was mit Nullwerten, Duplikaten, ungultigen historischen Datensatzen, Fremdschlusseln und teilweise migrierten Zeilen geschieht. Ein Backfill sollte, soweit sinnvoll, neu startbar sein. Ein Skript, das nur einmal und ohne Unterbrechung funktioniert, ist nach einem Timeout schwer wiederherzustellen.

5. Backup-, Sperr- und Beobachtungsfenster planen

Es muss gepruft werden, wie die genaue Datenbankversion die vorgesehene DDL ausfuhrt. Manche Operationen konnen eine stark genutzte Tabelle sperren oder neu aufbauen; die Wirkung hangt von Engine, Operation, Tabelle und Last ab. Erfundenen Zeitangaben ist auszuweichen. Signale fur Fortschritt und Fehler sollten vorab feststehen, und nach der Migration ist das Anwendungsverhalten zu prufen, statt einen Exit-Code von null fur den gesamten Test zu halten.

6. Einer Instanz die Migrationshoheit geben

Zwei Anwendungsinstanzen, die dieselbe ausstehende Migration gleichzeitig anwenden, konnen verwirrende Fehler erzeugen. Ein Deployment-Job, ein expliziter Wartungsbefehl oder ein Runner mit geeigneter Sperre sollte die Operation besitzen. Automatische Startup-Migrationen sind bequem, koppeln aber jeden Prozessstart an privilegierte Schemaarbeit. Diese Abwagung sollte bewusst erfolgen.

Was Migrationen nicht entscheiden konnen

Ein Migrationsrunner kann Skripte ordnen, Geschichte protokollieren, Prufsummen validieren und bei Fehlern stoppen. Er kann nicht entscheiden, ob abgeschnittener Text seine Bedeutung behalt, ob ein Indexaufbau bei Spitzenlast vertretbar ist oder ob zwei Darstellungen Datenschutzprobleme erzeugen. Das sind Anwendungs- und Betriebsentscheidungen.

Auch ein Framework ersetzt kein datenbankspezifisches Wissen. Doctrine, Flyway oder ein kleiner interner Runner konnen die Arbeit koordinieren, doch MariaDB bestimmt weiterhin, was seine DDL committet und sperrt. Das Werkzeug ist eine Checkliste mit Gedachtnis, kein Ersatz fur Datenbankverstandnis.

Fazit

Fur eine kleine PHP-Anwendung kann eine solide Migrationspraxis bescheiden beginnen: geordnete Dateien in der Versionskontrolle, ein vertrauenswurdiges Ledger, eine Migrationsautoritat, Tests vom vorherigen Zustand aus und ein zur Anderung passender Wiederherstellungsplan. Additive Anderungen konnen oft ausgerollt werden, bevor Code von ihnen abhangt; destruktive Anderungen sollten warten, bis alte Leser, Schreiber und Daten berucksichtigt sind.

Der zentrale Wandel besteht darin, Schemaanderungen nicht als beim Deployment eingetippte Befehle zu behandeln, sondern als Teil der Softwaregeschichte. Diese Geschichte macht Fehler nicht unmoglich. Sie macht den vorgesehenen Weg prufbar, wiederholbar und leichter hinterfragbar, bevor Produktionsdaten ihn beschreiten sollen.

References