Zero-Downtime-Migrationen
Übersicht
Schemaänderungsdisziplin, die ein laufendes System während eines Rolling-Deploys aufrechterhält. Während eines Rollouts teilen sich alte und neue Pods eine Datenbank – daher muss das Schema mit beiden App-Versionen gleichzeitig kompatibel sein. Eine destruktive Migration zusammen mit dem Deploy, der sie benötigt, ist der klassische selbstverschuldete Ausfall.
Erstellungsregeln (alle Projekte)
- Zwei Arten von Migrationen, getrennt gehalten: das idempotente
current/-Set (Entwicklungsiteration – wird bei jedem Durchlauf erneut ausgeführt,CREATE OR REPLACE/IF NOT EXISTS, Objekte in endgültiger Form, kein nachträglichesALTER) und committete Migrationen (unveränderlich, geordnet, was tatsächlich ausgeliefert wird).ALTER DEFAULT PRIVILEGESist das einzige akzeptierte Inline-ALTER. - Die Eigentümerschaft ergibt sich daraus, wie Migrationen ausgeführt werden, nicht aus verstreuten
ALTER ... OWNER. - Eine Bootstrap-Quelle der Wahrheit, die vom lokalen/Docker-Init-Pfad und der Shadow/Test-DB gemeinsam genutzt wird – jede Umgebung wird identisch aufgebaut.
Expand/Contract (Parallel Change)
Gilt für: wenn die Datenbank Daten enthält, die den Deploy überleben müssen (Staging-mit-Daten, Produktion). Ausgenommen: lokale/Reset-freundliche Projekte – aber schreibe committete Migrationen so, als ob dies gilt, damit eine Promotion nie eine Neuschreibung erfordert.
Eine Schemaänderung wird auf drei Releases aufgeteilt, nie auf einen:
- Expandieren – füge die neue Form additiv hinzu, abwärtskompatibel: neue nullable Spalte, neue Tabelle, neue Funktion. App-Version N läuft unverändert weiter.
- Migrieren – Backfill in begrenzten Batches (nie ein tabellensperrendes
UPDATE); Dual-Write aus App N+1 falls nötig; Lesevorgänge auf die neue Form umstellen. Erst jetztNOT NULLhinzufügen (ConstraintNOT VALIDsetzen, dannVALIDATEgetrennt durchführen). - Contracting – erst nachdem App N vollständig zurückgezogen ist, die alte Spalte/das alte Constraint in einem späteren Release entfernen.
Postgres-Fallen
| Falle | Sicherer Ansatz |
|---|---|
NOT NULL-Spalte mit volatilem/großem Backfill-Standardwert | Nullable hinzufügen → Backfill in Batches → Constraint NOT VALID hinzufügen → VALIDATE |
| Hinzufügen eines Constraints | Zuerst NOT VALID hinzufügen, dann VALIDATE CONSTRAINT (schwächere Sperre) |
| Erstellen eines Index | CREATE INDEX CONCURRENTLY – kann nicht in einer Transaktion ausgeführt werden; vom Transaktions-Wrapping des Migrationstools isolieren |
| Eine blockierte Migration friert die Produktion ein | lock_timeout / statement_timeout setzen, damit sie schnell fehlschlägt |
| Umbenennen einer Spalte | Es ist Expand/Contract: neue hinzufügen → Backfill → Lesevorgänge umstellen → alte löschen, über mehrere Releases |
Warum „CREATE OR REPLACE in Endform“ in der Produktion nicht ausreicht
Diese Regel dient der Entwicklerergonomie und ist in der Entwicklung korrekt. In der Produktion mit echten Nutzern und einem Rolling-Deploy teilen sich beide App-Versionen das Schema für die Dauer des Rollouts – daher sorgt Expand/Contract für die sichere Änderung. Vermische die beiden nicht.
Vollständige Begründung: Artikel XVII der Verfassung, gebündelt unter engineering-constitution/references/engineering-constitution.md. Deploy-Pipeline: cloud-delivery-aks.