Datenbank-Migrationen systematisch testen
AI generated
SELECT
JOIN
SQL · Testing · Migrationen · Qualitaetssicherung
Datenbank-Migrationen systematisch testen
vom Expand-Contract-Pattern bis zur Rollback-Probe

Wer Datenbank-Migrationen ungetestet gegen Produktion fahren laesst, riskiert Datenverlust, lange Downtime und stille Inkonsistenzen. Mit einer klaren Teststrategie aus Expand-Contract-Pattern, Rollback-Proben und automatisierten Pipelines werden Schema-Aenderungen planbar statt riskant.

17 Min. Lesezeit Expand-Contract · Rollback · CI-Pipeline · Produktionskopie PostgreSQL · MySQL · framework-agnostisch

1. Warum Datenbank-Migrationen ueberhaupt getestet werden muessen

Eine Schema-Migration wirkt auf den ersten Blick wie ein einfacher Schritt: eine Spalte hinzufuegen, einen Index anlegen, eine Fremdschluessel-Beziehung aendern. In der Praxis ist jede Migration jedoch ein Eingriff in ein System, das gleichzeitig produktiv genutzt wird, von mehreren Anwendungsversionen gelesen wird und Millionen Zeilen enthalten kann. Wer Migrationen ungetestet ausrollt, verlaesst sich darauf, dass die Syntax stimmt, ohne zu wissen, ob die Migration unter Produktionslast, mit echten Datenverteilungen und parallelen Schreibzugriffen genauso funktioniert wie lokal auf einer leeren Datenbank.

Die Folgen ungetesteter Migrationen reichen von Minuten langem Tabellen-Lock, das den gesamten Checkout-Prozess eines Shops blockiert, bis zu stillem Datenverlust, weil eine NOT NULL-Spalte ohne Standardwert bestehende Zeilen unbrauchbar macht. Migrationen systematisch zu testen bedeutet, genau diese Szenarien vor dem Produktions-Deploy durchzuspielen: mit realistischen Datenmengen, mit gleichzeitigem Anwendungszugriff und mit einem klaren Rollback-Pfad fuer den Fall, dass etwas schiefgeht. Die folgenden Abschnitte zeigen, wie sich Migrationstests strukturieren, automatisieren und in bestehende Pipelines integrieren lassen.

2. Die Testpyramide fuer Schema-Migrationen

Migrationstests folgen demselben Grundprinzip wie jede andere Testpyramide: viele schnelle, isolierte Tests an der Basis, wenige teure End-to-End-Tests an der Spitze. Auf unterster Ebene stehen Syntax- und Idempotenz-Tests, die pruefen, ob eine Migration ueberhaupt fehlerfrei gegen eine leere Datenbank laeuft und ob ein zweiter Lauf der gleichen Migration keinen Fehler wirft. Diese Tests laufen in Millisekunden und fangen die offensichtlichsten Fehler ab, bevor komplexere Tests ueberhaupt gestartet werden.

Auf der mittleren Ebene stehen Migrationstests gegen eine Datenbank mit repraesentativen Testdaten: mehrere tausend Zeilen, Randfaelle wie NULL-Werte, sehr lange Strings, Unicode-Zeichen und referenzielle Beziehungen ueber mehrere Tabellen. Hier zeigt sich, ob eine Migration mit echten Daten kollidiert, etwa wenn ein neuer UNIQUE-Constraint auf eine Spalte gesetzt wird, die in der Testdatenbank bereits Duplikate enthaelt. An der Spitze der Pyramide steht der vollstaendige Migrationstest gegen eine anonymisierte Kopie der Produktionsdatenbank, inklusive Zeitmessung, Lock-Analyse und Rollback-Probe. Diese Tests sind teuer und laufen seltener, liefern aber die verlaesslichste Aussage darueber, wie sich die Migration in Produktion verhalten wird.


-- Layer 1: idempotency check for a migration
-- Run the migration twice against an empty schema, expect no error on the second run
BEGIN;
ALTER TABLE customers ADD COLUMN IF NOT EXISTS loyalty_tier VARCHAR(20);
CREATE INDEX IF NOT EXISTS idx_customers_loyalty_tier ON customers (loyalty_tier);
COMMIT;

-- Layer 2: constraint collision check against representative test data
-- This must run BEFORE adding the constraint in production
SELECT email, COUNT(*) AS occurrences
FROM customers
GROUP BY email
HAVING COUNT(*) > 1;
-- Any row returned here means the UNIQUE constraint will fail on deploy

3. Expand-Contract-Pattern: Kompatibilitaet in beide Richtungen pruefen

Das Expand-Contract-Pattern, manchmal auch Parallel-Change-Pattern genannt, ist die zentrale Technik, um Datenbank-Migrationen ohne Downtime zu testen und auszurollen. Statt eine Spalte in einem Schritt umzubenennen oder zu entfernen, wird die Aenderung in mehrere, jeweils fuer sich rueckwaertskompatible Schritte zerlegt: zuerst die neue Struktur hinzufuegen (Expand), dann beide Strukturen parallel befuellen, dann die alte Struktur entfernen (Contract). Jede einzelne Phase laesst sich isoliert testen, weil sie fuer sich genommen abwaertskompatibel zur laufenden Anwendungsversion bleibt.

Der entscheidende Testfall bei diesem Pattern ist die Kompatibilitaetspruefung zwischen zwei Anwendungsversionen: Waehrend eines rollierenden Deployments laufen alte und neue Codeversion gleichzeitig gegen dieselbe Datenbank. Ein systematischer Migrationstest simuliert genau das, indem er die Migration ausfuehrt und danach beide Anwendungsversionen gegen das Schema testet, nicht nur die neue. Wird diese Pruefung uebersprungen, faellt ein Kompatibilitaetsbruch erst in Produktion auf, wenn bereits ein Teil der Server auf altem Code laeuft.


-- Expand phase: add new column, keep old column untouched
ALTER TABLE orders ADD COLUMN shipping_address_id BIGINT NULL;

-- Backfill phase: populate new column from old data, both stay in sync
UPDATE orders
SET shipping_address_id = (
  SELECT id FROM addresses
  WHERE addresses.order_id = orders.id
  LIMIT 1
)
WHERE shipping_address_id IS NULL;

-- Contract phase: only after ALL app instances read the new column,
-- drop the old one in a separate, later migration
-- ALTER TABLE orders DROP COLUMN legacy_address_text;

4. Migrationen gegen eine realistische Datenkopie testen

Migrationen auf einer leeren Testdatenbank zu pruefen, sagt wenig darueber aus, wie sie sich gegen echte Datenmengen verhalten. Ein ALTER TABLE, das lokal in Millisekunden laeuft, kann gegen eine Produktionstabelle mit fuenfzig Millionen Zeilen mehrere Minuten dauern und dabei einen exklusiven Lock halten, der jeden Schreibzugriff blockiert. Der zuverlaessigste Weg, das vorab zu erkennen, ist ein regelmaessig aktualisierter, anonymisierter Snapshot der Produktionsdatenbank als Staging-Umgebung fuer Migrationstests.

Bei der Anonymisierung ist wichtig, dass die statistische Verteilung der Daten erhalten bleibt, nicht nur die Struktur. Ein Snapshot, bei dem alle E-Mail-Adressen durch denselben Platzhalter ersetzt wurden, verhaelt sich bei einem neuen UNIQUE-Index anders als echte Produktionsdaten mit ihrer natuerlichen Verteilung von Duplikaten und Sonderzeichen. Tools wie pg_sample fuer PostgreSQL oder eigene Anonymisierungs-Skripte, die Werte deterministisch durch realistische Zufallswerte ersetzen, halten die Datenverteilung nah am Original, ohne echte personenbezogene Daten preiszugeben. Migrationstests gegen einen solchen Snapshot zeigen Lock-Dauer, IO-Last und Speicherverbrauch, die auf einer leeren Datenbank unsichtbar bleiben.

5. Automatisierte Migrationstests in der CI-Pipeline

Manuelles Testen von Migrationen skaliert nicht, sobald ein Team mehrmals pro Woche Schema-Aenderungen ausrollt. Der pragmatische Weg ist, jede Migration automatisch in der CI-Pipeline gegen eine frische Datenbankinstanz laufen zu lassen, bevor der Merge erlaubt wird. Ein typischer Pipeline-Schritt startet einen Datenbank-Container, spielt alle bestehenden Migrationen bis zum aktuellen Stand ein, laedt einen Satz Testdaten und fuehrt dann die neue Migration aus, gefolgt von einem Rollback und einem zweiten Forward-Lauf.

Dieser Zyklus aus Forward, Rollback und erneutem Forward deckt zwei haeufige Fehlerquellen ab: fehlende oder fehlerhafte Down-Migrationen und Migrationen, die beim zweiten Ausfuehren nicht idempotent sind. Ergaenzend lohnt sich ein automatisierter Schema-Diff-Check nach der Migration, der das tatsaechliche Schema mit dem erwarteten Zielzustand vergleicht, um Drift zwischen Migrationsskripten und echtem Datenbankzustand fruehzeitig zu erkennen.


#!/usr/bin/env bash
# ci-migration-check.sh — run in CI before merging a schema migration
set -euo pipefail

echo "[1/4] Starting fresh database container"
docker compose up -d db
sleep 5

echo "[2/4] Applying all existing migrations up to current HEAD"
migrate -database "$DB_URL" -path ./migrations up

echo "[3/4] Loading representative seed data"
psql "$DB_URL" -f ./tests/fixtures/seed_representative.sql

echo "[4/4] Testing new migration: forward, rollback, forward again"
migrate -database "$DB_URL" -path ./migrations up 1
migrate -database "$DB_URL" -path ./migrations down 1
migrate -database "$DB_URL" -path ./migrations up 1

echo "Schema diff check against expected target state"
./tools/schema-diff.sh --expected ./schema/target.sql --actual "$DB_URL"

6. Rollback-Strategien und ihre Testbarkeit

Eine Migration ohne getesteten Rollback-Pfad ist im Ernstfall ein Blindflug. Ein Rollback ist nicht automatisch die einfache Umkehrung der Forward-Migration: Wurde eine Spalte entfernt, kann der Rollback die Daten darin nicht wiederherstellen, es sei denn, sie wurden vorher in eine Archivtabelle kopiert. Rollback-Skripte muessen deshalb genauso getestet werden wie die Forward-Migration selbst, mit demselben Datensatz, um zu pruefen, ob nach dem Rollback tatsaechlich der vorherige Zustand erreicht wird.

Fuer irreversible Operationen wie das Loeschen einer Spalte oder das Aendern eines Datentyps mit Praezisionsverlust gilt: der Rollback-Test muss explizit pruefen, welche Daten im Rollback-Fall verloren gehen, und dieses Risiko dokumentieren, statt es stillschweigend hinzunehmen. Ein bewaehrtes Muster ist, destructive Operationen erst in einer spaeteren, separaten Migration auszufuehren, nachdem die vorherige Migration bereits laengere Zeit stabil in Produktion lief und ein Rollback ohnehin nicht mehr sinnvoll waere.


-- Safe rollback pattern: archive data BEFORE the destructive forward migration
-- Forward migration (up):
CREATE TABLE customers_legacy_notes_archive AS
SELECT id AS customer_id, legacy_notes, NOW() AS archived_at
FROM customers;

ALTER TABLE customers DROP COLUMN legacy_notes;

-- Rollback migration (down): restore from the archive, not from thin air
ALTER TABLE customers ADD COLUMN legacy_notes TEXT NULL;

UPDATE customers
SET legacy_notes = archive.legacy_notes
FROM customers_legacy_notes_archive AS archive
WHERE archive.customer_id = customers.id;
-- Without the archive step, this rollback would only be able to add
-- back an empty column, not the original data

7. Locking- und Performance-Auswirkungen testen

Viele Migrationsfehler entstehen nicht durch falsche Syntax, sondern durch unerwartetes Sperrverhalten. Ein ALTER TABLE ADD COLUMN NOT NULL DEFAULT auf einer grossen Tabelle kann je nach Datenbanksystem eine vollstaendige Tabellenumschreibung ausloesen und dabei einen exklusiven Lock ueber die gesamte Dauer halten. In PostgreSQL ab Version 11 ist das Hinzufuegen einer Spalte mit konstantem Default zwar eine reine Metadatenaenderung ohne Tabellen-Rewrite, aber ein zusaetzlicher CHECK-Constraint mit NOT VALID gefolgt von VALIDATE CONSTRAINT verhaelt sich wieder anders und muss separat getestet werden.

Ein systematischer Migrationstest misst deshalb nicht nur, ob die Migration erfolgreich durchlaeuft, sondern auch, wie lange sie welchen Lock-Typ haelt. Werkzeuge wie pg_stat_activity in PostgreSQL oder SHOW PROCESSLIST in MySQL, waehrend die Migration parallel zu simulierten Anwendungsanfragen laeuft, zeigen, ob Anfragen blockiert werden. Bei Tabellen mit hoher Schreiblast lohnt sich zusaetzlich ein Test mit Tools wie gh-ost oder pt-online-schema-change fuer MySQL, die Migrationen ueber eine Schattentabelle ohne langen Lock durchfuehren, aber selbst wieder eigene Testfaelle brauchen, etwa fuer den Umgang mit gleichzeitigen Trigger-Aenderungen.

8. Stille Datenkorruption und Datenverlust erkennen

Die gefaehrlichsten Migrationsfehler sind nicht die, die einen Fehler werfen, sondern die, die stillschweigend Daten veraendern oder verlieren. Eine Typaenderung von VARCHAR(255) auf VARCHAR(50) ohne vorherige Pruefung schneidet laengere Werte ab, ohne dass die Datenbank zwingend einen Fehler meldet, je nach Strict-Mode-Konfiguration. Eine Umwandlung von DECIMAL in FLOAT fuehrt zu Rundungsfehlern, die erst Wochen spaeter in Finanzberichten auffallen.

Ein systematischer Migrationstest sollte deshalb vor und nach der Migration Pruefsummen ueber kritische Spalten berechnen und vergleichen, etwa mit SUM(), COUNT(DISTINCT ...) oder einem Hash ueber sortierte Zeilen. Weicht die Pruefsumme nach der Migration ab, obwohl keine Aenderung an den Werten beabsichtigt war, ist das ein starkes Signal fuer stille Datenkorruption. Diese Pruefung laesst sich in dieselbe CI-Pipeline integrieren, die auch die Forward- und Rollback-Tests ausfuehrt, und liefert damit einen zusaetzlichen, datenbasierten Sicherheitsnetz ueber die reine Syntaxpruefung hinaus.


-- Data integrity checksum before and after a migration
-- Run this before AND after applying the migration, compare results
SELECT
  COUNT(*)                          AS row_count,
  SUM(total_amount)                 AS sum_total_amount,
  COUNT(DISTINCT customer_id)       AS distinct_customers,
  MD5(STRING_AGG(id::text, ',' ORDER BY id)) AS row_order_hash
FROM orders;
-- Any unexplained difference after the migration signals silent data loss

9. Teststrategien fuer Migrationen im Vergleich

Nicht jede Teststrategie passt zu jeder Migrationsgroesse. Kleine, additive Aenderungen brauchen weniger Aufwand als destructive Operationen auf grossen Produktionstabellen. Die folgende Tabelle ordnet die wichtigsten Ansaetze nach Aufwand und Aussagekraft ein.

Teststrategie Aufwand Erkennt zuverlaessig Einsatzempfehlung
Syntax-Check auf leerer DB Sehr niedrig Tippfehler, ungueltige Anweisungen Immer, in jedem Commit
Test mit repraesentativen Fixtures Niedrig Constraint-Kollisionen, Randfaelle Bei jeder neuen Migration
Forward/Rollback/Forward-Zyklus Niedrig bis mittel Fehlende Down-Migration, Nicht-Idempotenz In jeder CI-Pipeline
Test gegen anonymisierten Produktions-Snapshot Mittel bis hoch Lock-Dauer, Laufzeit, IO-Last Vor kritischen Migrationen
Checksummen-Vergleich vor/nach Niedrig Stille Datenkorruption Bei jeder datenverändernden Migration

In der Praxis kombiniert man mehrere dieser Strategien je nach Risiko der Migration. Eine reine Indexerstellung braucht selten mehr als den Syntax-Check und einen Laufzeittest, waehrend eine Typaenderung an einer zentralen Tabelle den vollen Umfang inklusive Checksummen-Vergleich und Produktions-Snapshot rechtfertigt. Wer diese Eskalationsstufen im Team dokumentiert, vermeidet sowohl unnoetigen Testaufwand bei trivialen Aenderungen als auch fehlenden Testaufwand bei kritischen.

Mironsoft

Datenbank-Migrationen, Testautomatisierung und CI/CD fuer Magento und darueber hinaus

Schema-Migrationen, die im Live-Betrieb nicht ueberraschen?

Wir bauen Migrationstest-Pipelines, die Rollback-Faehigkeit, Lock-Verhalten und Datenintegritaet automatisch pruefen, bevor eine Aenderung in Produktion geht.

Migrationstest-Setup

Forward-Rollback-Zyklen und Checksummen-Vergleiche in der CI-Pipeline verankern

Produktions-Snapshots

Anonymisierte, realistische Testdatenbanken fuer aussagekraeftige Migrationstests

Zero-Downtime-Deploys

Expand-Contract-Pattern fuer Schema-Aenderungen ohne Ausfallzeit

10. Zusammenfassung

Datenbank-Migrationen systematisch zu testen bedeutet, sie nicht als reinen Syntax-Vorgang zu behandeln, sondern als Eingriff in ein produktives System mit echten Daten, echter Last und echten Anwendungsversionen im Parallelbetrieb. Die Testpyramide aus Syntax-Check, Fixture-Test und Produktions-Snapshot deckt unterschiedliche Risikostufen ab. Das Expand-Contract-Pattern macht Migrationen in beide Richtungen kompatibel testbar, ohne Downtime zu erzwingen.

Automatisierte CI-Pipelines mit Forward-Rollback-Forward-Zyklen und Checksummen-Vergleichen fangen die haeufigsten Fehlerquellen ab: fehlende Down-Migrationen, Nicht-Idempotenz und stille Datenkorruption. Wer zusaetzlich Lock-Dauer und Performance gegen eine realistische Datenkopie misst, geht mit einer belastbaren Datenbasis statt mit Vermutungen in den Produktions-Deploy. Das Ergebnis: Migrationen werden von einem Risikofaktor zu einem planbaren, wiederholbaren Teil des Entwicklungsprozesses.

Datenbank-Migrationen testen — Das Wichtigste auf einen Blick

Testpyramide

Syntax-Check, Fixture-Test und Produktions-Snapshot decken unterschiedliche Risikostufen einer Migration ab.

Expand-Contract

Aenderungen in rueckwaertskompatible Teilschritte zerlegen, jede Phase einzeln testbar halten.

CI-Automatisierung

Forward-Rollback-Forward-Zyklus in jeder Pipeline, um fehlende Down-Migrationen fruehzeitig zu erkennen.

Datenintegritaet

Checksummen vor und nach der Migration vergleichen, um stille Datenkorruption zu erkennen.

11. FAQ: Datenbank-Migrationen systematisch testen

1Warum reicht ein Test auf leerer DB nicht?
Constraint-Kollisionen, Lock-Dauer und Performance-Effekte zeigen sich erst mit realistischer Datenmenge, nicht auf einer leeren Datenbank.
2Was ist Expand-Contract?
Aenderungen in rueckwaertskompatible Teilschritte zerlegen: neue Struktur hinzufuegen, parallel befuellen, alte Struktur spaeter entfernen.
3Braucht jede Migration einen Rollback-Test?
Ja, ein Forward-Rollback-Forward-Zyklus in der CI-Pipeline. Bei destructive Operationen zusaetzlich pruefen, welche Daten dabei verloren gehen.
4Wie testet man Lock-Verhalten?
Gegen realistische Datenkopie mit parallelen simulierten Anfragen laufen lassen, mit pg_stat_activity oder SHOW PROCESSLIST beobachten.
5Wie erkennt man stille Datenkorruption?
Checksummen ueber kritische Spalten vor und nach der Migration vergleichen. Unerwartete Abweichungen sind ein starkes Warnsignal.
6Warum eine anonymisierte Produktionskopie?
Synthetische Daten haben oft eine andere Verteilung. Eine anonymisierte Kopie erhaelt die reale Verteilung und macht Tests aussagekraeftiger.
7Metadaten-Change vs. Table-Rewrite?
Ein Metadaten-Change ist sofort fertig, ein Table-Rewrite schreibt die ganze Tabelle neu und haelt laenger einen exklusiven Lock. Getrennt testen.
8Wann gh-ost oder pt-online-schema-change?
Bei grossen, stark geschriebenen MySQL-Tabellen. Arbeiten ueber eine Schattentabelle, brauchen aber eigene Tests fuer Trigger-Interaktion.
9Wie oft den Produktions-Snapshot aktualisieren?
Regelmaessig, idealerweise automatisiert und woechentlich, damit die Datenverteilung nicht veraltet und aussagekraeftig bleibt.
10Braucht jede Migration den vollen Testumfang?
Nein, der Testumfang sollte am Risiko der Migration ausgerichtet sein: Indexerstellung braucht weniger als eine Typaenderung an zentralen Tabellen.