Zum Inhalt springen

Die Migration, die die Seite offline genommen hat

Eine Spalte hinzuzufügen ist umsonst. Eine mit Standardwert oder ein Index nimmt eine Sperre - und auf einer großen Tabelle überdauert sie den Anfrage-Timeout, während das Deployment Erfolg meldet.

4 Min. Lesezeit

Das Deployment ging um zehn nach vier raus. Die Pipeline war grün, die Migration meldete Erfolg, und die Seite lieferte mittendrin sechs Minuten lang Fehler aus.

Nichts ist gescheitert. Eine Tabelle war gesperrt, während sie umgeschrieben wurde, jede Anfrage, die sie berührte, stellte sich hinter die Sperre, und die Schlange wuchs schneller, als sie abfloss, bis der Verbindungspool leer war.

Welche Operationen umsonst sind und welche nicht

Der entscheidende Unterschied ist, ob die Datenbank die Metadaten der Tabelle ändern kann oder ihre Zeilen umschreiben muss.

Auf beiden großen Engines im Allgemeinen sicher:

  • Eine nullbare Spalte ohne Standardwert hinzufügen
  • Eine Spalte löschen
  • Eine Tabelle umbenennen
  • Eine Constraint hinzufügen, die nicht sofort validiert wird, wo unterstützt

Auf einer großen Tabelle im Allgemeinen nicht sicher:

  • Eine Spalte mit Standardwert hinzufügen, auf älteren Engines
  • Den Typ einer Spalte ändern
  • Einen Index ohne die nebenläufige oder Online-Option hinzufügen
  • Einen Fremdschlüssel hinzufügen, der jede vorhandene Zeile validiert
  • Alles, was den Primärschlüssel ändert

Die Versionen zählen hier, und die Details ebenso: Aktuelles MySQL und MariaDB beherrschen das sofortige Hinzufügen von Spalten in vielen Fällen, und Postgres fügt seit Version 11 eine nullbare Spalte mit Standardwert billig hinzu. Der Punkt ist nicht, die Matrix auswendig zu lernen, sondern zu wissen, auf welcher Seite davon Ihre Migration steht – bevor sie gegen die Produktion läuft.

Die, die überrascht

Schema::table('orders', function (Blueprint $table) {
    $table->index('customer_id');
});

Einen Index hinzuzufügen sieht nach Lesen aus, nicht nach Schreiben. Auf MySQL ohne Online-DDL-Pfad und auf Postgres ohne CONCURRENTLY nimmt es für die Dauer des Aufbaus eine Sperre – auf einer großen Tabelle sind das Minuten.

Postgres bietet die nebenläufige Option, und Laravel gibt sie nicht aus, sie muss also von Hand geschrieben werden:

public function up(): void
{
    DB::statement('CREATE INDEX CONCURRENTLY orders_customer_id_index ON orders (customer_id)');
}

Aus dieser Anweisung folgen zwei Dinge. Sie kann nicht in einer Transaktion laufen, die Migration muss sich also aus der Umhüllung abmelden. Und sie kann auf halbem Weg scheitern und einen ungültigen Index hinterlassen, der vor einem neuen Versuch gelöscht werden muss – was besser vorher bekannt ist als mittendrin.

Eine Spalte ändern, ohne die Tabelle umzuschreiben

Die meisten Typänderungen – einen Integer verbreitern, die Länge eines Varchar ändern, einen Status von einer Zeichenkette zu einer Referenz machen – gehen ohne eine einzige sperrende Operation, in mehr Schritten, als sie zu brauchen scheinen:

  1. Die neue Spalte hinzufügen, nullbar, ohne Standardwert. Sofort.
  2. In der Anwendung in beide Spalten schreiben, das ausliefern und laufen lassen.
  3. Die alten Zeilen in Stapeln auf einer Queue nachfüllen, mit einer Pause zwischen den Stapeln, damit Replikation und übriger Traffic Luft bekommen.
  4. Prüfen, dass beide übereinstimmen – die Zahl der Zeilen, in denen sie abweichen, muss null sein.
  5. Lesen umstellen. Ausliefern. Warten.
  6. Aufhören, die alte zu beschreiben. Ausliefern.
  7. Sie in einem späteren Release löschen.

Sieben Deployments statt einer Zeile. Es sind auch sieben Deployments, während derer die Seite oben bleibt und von denen jedes gestoppt oder umgekehrt werden kann – der Handel, der ohnehin gemacht wird, ob ihn jemand benennt oder nicht.

Beim Nachfüllen liegt die Sorgfalt. Ein einzelnes UPDATE über zehn Millionen Zeilen ist genau die Sperre, die Sie vermeiden wollten, mit einem anderen Hut:

Order::whereNull('customer_uuid')
    ->select('id')
    ->chunkById(1000, function ($orders) {
        Order::whereIn('id', $orders->pluck('id'))
            ->update([/* ... */]);
 
        usleep(100_000);
    });

Regeln, die sich lohnen

Jede Migration erklärt, ob sie die Tabelle umschreibt. Ein Kommentar oben, ehrlich beantwortet, erzwingt die Frage im Review statt in der Produktion.

Alles, was umschreibt, läuft getrennt vom Deployment. Beobachtet, von jemandem, mit der Möglichkeit es zu stoppen.

Nichts, was etwas entfernt, wird mit dem Code ausgeliefert, der aufhört es zu benutzen. Eine gelöschte Spalte bricht jeden Prozess, der noch das vorige Release fährt – Queue-Worker eingeschlossen, die ihren Code halten, bis sie neu gestartet werden.

Kennen Sie Ihr Lock-Timeout. Eine Migration, die nach dreißig Sekunden aufgibt, ist enorm viel besser als eine, die unbegrenzt wartet, während sich Anfragen dahinter stapeln. Eines zu setzen verwandelt einen Ausfall in eine gescheiterte Migration.

Das Letzte ist die billigste Versicherung in diesem ganzen Artikel, und es ist eine Zeile Konfiguration, die die meisten Anwendungen nie gesetzt haben.

Welche Operationen sperren und wie lange bei Ihren Zeilenzahlen, misst ein Datenbankprojekt an Ihren Daten. Schätzungen helfen hier wenig. Wenn Sie noch eine Engine auswählen und das den Ausschlag gibt, sind die Unterschiede nach Engine aufgeschlüsselt.

Verwandte Fragen

Betrifft das Postgres genauso wie MySQL?
Beide, unterschiedlich. Postgres kann eine nullbare Spalte sofort hinzufügen und einen Index nebenläufig bauen, braucht aber trotzdem eine kurze exklusive Sperre zum Start - und diese Sperre stellt sich hinter lange Transaktionen an, wodurch eine "sichere" Migration eine stark genutzte Tabelle blockiert. MySQL hat Online-DDL für viele Operationen und nicht für alle.
Ab welcher Größe muss man sich Sorgen machen?
Es gibt keine Zeilenzahl, die es sicher macht, denn entscheidend ist, wie lange die Operation dauert gegenüber dem, wie lange Ihr Traffic warten kann. Eine Million Zeilen auf schnellem Speicher können Sekunden sein; dieselbe Tabelle mit hoher Schreiblast und einem lange offenen Report ist eine andere Antwort.
Können wir Migrationen nicht einfach in einem Wartungsfenster fahren?
Können Sie, und für ein Unternehmen mit ruhigen Stunden ist das eine völlig gute Antwort. Die meisten Techniken hier existieren für Systeme, die keine haben - und selbst mit Fenster ist der nützliche Teil zu wissen, welche Operationen eines brauchen.
Sollten Migrationen beim Deployment automatisch laufen?
Für die Routinefälle meist ja, denn ein manueller Schritt ist ein vergessener Schritt. Die Ausnahme sind genau die hier besprochenen Operationen: Alles, was eine Tabelle umschreibt, will bewusst gestartet, beobachtet und vom Deployment getrennt werden, das darauf aufbaut.

← Zurück zu allen Artikeln

Anrufen+1 848 272 7583WhatsApp+90 850 308 5436E-Mailinfo@codefacture.comKontaktseite