Use Cases (Aktualisiert: 2.6.2026)

Datenbankmigrationen mit Claude Code sicher in Produktion

Praxisguide für Claude Code bei DB-Migrationen: expand/contract, Prisma, CI, Backfill, Locks und Rollback-Grenzen.

Datenbankmigrationen mit Claude Code sicher in Produktion

Eine Datenbankmigration in Produktion sollte nicht mit einem ungenauen Prompt wie “ändere das Schema” beginnen. Das Risiko liegt nicht nur in der SQL-Syntax. Es liegt in der Reihenfolge: App-Deployment, Datenbank-Locks, Backups, Backfill, Feature Flags, CI-Prüfungen und Rollback-Entscheidungen greifen ineinander.

Claude Code ist hilfreich, wenn es zuerst als Reviewer und dann als Implementierungshelfer arbeitet. Gefährlich wird es, wenn ein Agent eine große Migration erzeugt und das Team sie ohne Aufteilung übernimmt. Dieser Guide richtet sich an Teams mit PostgreSQL und Prisma Migrate, funktioniert aber auch für reine SQL-Migrationsordner.

Nutze beim Review die offiziellen Quellen: Claude Code Dokumentation, PostgreSQL zu explicit locking und ALTER TABLE, Prisma zu development and production und CLI reference, sowie GitHub Actions workflow syntax.

Expand/Contract

Das Grundmodell heißt expand/contract. Expand bedeutet: Die Datenbank wird so erweitert, dass alte und neue App-Version gleichzeitig funktionieren. Contract bedeutet: Alte Spalten, alte Lesepfade oder temporäre Constraints werden erst entfernt, wenn der neue Pfad in Produktion stabil ist.

flowchart LR
  A["Backup and review"]
  B["Expand: add nullable column or new table"]
  C["Deploy code with dual write or feature flag"]
  D["Backfill data in small batches"]
  E["Validate staging and production metrics"]
  F["Contract: add NOT NULL, remove old path"]
  A --> B --> C --> D --> E --> F

Der typische Fehler: Claude Code fügt eine Spalte hinzu, kopiert Daten, setzt NOT NULL und löscht die alte Spalte in einer einzigen Migration. Lokal kann das funktionieren. Auf einer users- oder orders-Tabelle mit Millionen Zeilen kann es lange Locks, blockierte Writes, Timeouts oder Datenverlust auslösen.

Ein paar Begriffe müssen klar sein. Ein Lock ist ein Datenbankmechanismus, der nicht kompatible parallele Änderungen verhindert. Ein Backfill füllt neue Spalten für bestehende Zeilen. Eine shadow database ist eine temporäre Datenbank, mit der Prisma in der Entwicklung die Migrationshistorie nachspielt und Drift erkennt. Sie ist kein Produktionsschutz.

Erst Review, Dann Code

Der erste Prompt sollte keine Dateien ändern. Gib Claude Code Tabellengrößen, Deployment-Art, ORM und Erwartungen an Rollback und Recovery.

Review this database migration plan before editing files.

Context:
- Production database: PostgreSQL
- ORM: Prisma Migrate
- Hot tables: users has about 8 million rows, orders has about 25 million rows
- Deploy style: blue/green app deploy, database migration runs in CI/CD
- Requirement: split users.name into users.full_name and users.display_name

Check:
1. Can old and new app versions run at the same time?
2. Which SQL statements may take strong locks or scan the whole table?
3. Which steps must be expand, backfill, validate, and contract?
4. What backup or point-in-time recovery check is needed before deploy?
5. What can be rolled back by app deploy, and what can only be rolled forward?

Return a migration plan first. Do not edit files yet.

Der letzte Satz ist wichtig. Claude Code ist schnell, aber Datenbankarbeit braucht eine bewusste Pause. Wenn der erste Plan riskante Schritte mischt, schärfe den Auftrag.

Rewrite the plan so that no step drops a column, rewrites a large table, or sets NOT NULL before the backfill is verified. Include a staging rehearsal and a production abort condition.

So wird Claude Code zu einem Migration-Reviewer. Der Mensch bewertet das Risiko, der Agent sammelt Dateien, Kommandos, SQL, CI-Prüfungen und Monitoring-Fragen.

Expand-SQL

Nehmen wir an, users.name soll durch full_name und display_name ersetzt werden. Die Expand-Migration fügt nur nullable Spalten und einen Index hinzu. Kein Backfill, kein NOT NULL, kein Löschen der alten Spalte.

-- 20260602090000_expand_users_names.sql
-- Keep this migration small. Do not backfill and do not drop users.name here.

ALTER TABLE users
  ADD COLUMN full_name text,
  ADD COLUMN display_name text;

-- Run outside a transaction in PostgreSQL migration tools that support it.
-- CREATE INDEX CONCURRENTLY cannot run inside a transaction block.
CREATE INDEX CONCURRENTLY IF NOT EXISTS users_display_name_idx
  ON users (display_name);

Die PostgreSQL-Dokumentation zu ALTER TABLE beschreibt, dass Subkommandos unterschiedliche Lock-Level haben können. Wenn kein leichteres Lock dokumentiert ist, sollte das Review konservativ sein. Claude Code soll Lock-Risiko nicht raten, sondern mit der offiziellen Dokumentation abgleichen.

Mit Prisma erzeugst du die Migration zuerst ohne Anwendung und prüfst das SQL.

npx prisma migrate dev --name expand-users-names --create-only
npx prisma validate

Prisma dokumentiert npx prisma migrate deploy für Test- und Produktionsumgebungen. Die gleiche Quelle weist darauf hin, dass migrate deploy pending migrations anwendet, aber Drift nicht erkennt und keine shadow database nutzt. Ein erfolgreicher Deploy-Befehl ist also keine vollständige Produktionsprobe.

npx prisma migrate deploy

App-Kompatibilität

Nach expand muss die Anwendung beide Schemata aushalten. Reads brauchen einen Fallback, Writes sollten im Übergang alte und neue Felder befüllen.

// src/domain/userName.ts
type UserNameRow = {
  name: string | null;
  fullName: string | null;
  displayName: string | null;
};

export function readDisplayName(user: UserNameRow): string {
  return user.displayName ?? user.fullName ?? user.name ?? "Unknown user";
}

export function buildNameUpdate(input: { name: string }) {
  const normalized = input.name.trim().replace(/\s+/g, " ");

  return {
    name: normalized,
    fullName: normalized,
    displayName: normalized.length > 40 ? `${normalized.slice(0, 39)}...` : normalized,
  };
}

Ein Feature Flag trennt Datenbankänderung und sichtbares Verhalten. Der neue Lesepfad bleibt aus, bis Backfill und Validierung durch sind. Wenn Fehler auftreten, lässt sich der Flag zurücknehmen, ohne die Datenbank sofort zu verändern. Die Umsetzung passt zum internen Feature-Flags-Guide.

Das Muster passt zu mindestens drei produktiven Fällen: Profilfelder umbenennen oder aufteilen, berechnete Spalten wie Rechnungssaldo oder Suchlabel einführen, sowie Indizes oder Foreign Keys auf heißen Tabellen ergänzen. In allen Fällen senkt die Aufteilung das Risiko.

Backfill In Batches

Ein riesiges UPDATE kann Lock-Zeit, WAL-Volumen, Replikationsverzögerung und Betriebsrisiko erhöhen. Besser ist ein neu startbares Batch-Skript.

// scripts/backfill-user-names.mjs
import pg from "pg";

const { Client } = pg;
const batchSize = Number(process.env.BATCH_SIZE ?? 1000);
const sleepMs = Number(process.env.SLEEP_MS ?? 200);

const client = new Client({ connectionString: process.env.DATABASE_URL });

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

await client.connect();

try {
  let total = 0;

  while (true) {
    const result = await client.query(
      `
      WITH target AS (
        SELECT id, name
        FROM users
        WHERE full_name IS NULL
          AND name IS NOT NULL
        ORDER BY id
        LIMIT $1
        FOR UPDATE SKIP LOCKED
      )
      UPDATE users AS u
      SET
        full_name = target.name,
        display_name = CASE
          WHEN length(target.name) > 40 THEN substring(target.name from 1 for 39) || '...'
          ELSE target.name
        END
      FROM target
      WHERE u.id = target.id
      RETURNING u.id
      `,
      [batchSize],
    );

    total += result.rowCount;
    console.log(`updated=${result.rowCount} total=${total}`);

    if (result.rowCount === 0) break;
    await sleep(sleepMs);
  }
} finally {
  await client.end();
}

Vor Produktion prüfst du Idempotenz, Teilfehler, parallele Ausführung und Betriebssteuerung. Ein gutes Backfill hat Batch-Größe, Pausen, Logs, Abbruchmöglichkeit und eine Query, die wiederholt laufen kann.

CI Und Staging

CI sollte die gesamte Migrationshistorie auf einer temporären Datenbank anwenden. Bei GitHub Actions liegen Workflows als YAML unter .github/workflows.

name: migration-check

on:
  pull_request:
    paths:
      - "prisma/**"
      - "scripts/backfill-*.mjs"
      - ".github/workflows/migration-check.yml"

jobs:
  prisma-migrations:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: app
        ports:
          - "5432:5432"
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    env:
      DATABASE_URL: postgresql://postgres:postgres@localhost:5432/app?schema=public

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: npm
      - run: npm ci
      - run: npx prisma validate
      - run: npx prisma migrate deploy
      - run: npx prisma migrate status
      - name: Detect schema drift after migrations
        run: |
          npx prisma migrate diff \
            --exit-code \
            --from-config-datasource \
            --to-schema=prisma/schema.prisma

Dieses Beispiel nutzt Prisma ORM v7 Syntax mit config datasource. Kopiere keine alten Beispiele mit --from-url oder --shadow-database-url, ohne die aktuelle CLI-Referenz zu prüfen.

Staging muss realitätsnäher sein als CI: ähnliche Zeilenzahl, ähnliche Indizes, ähnliche Timeouts und derselbe Migration-Runner. Lass Claude Code eine Probe-Checkliste für Lock Waits, Replikationslag, Latenz, App-Logs und Abbruchschwellen schreiben.

Contract Und Rollback-Grenzen

Contract kommt erst nach stabiler App-Version und validiertem Backfill. Für NOT NULL ist eine Validierungsstufe sinnvoll.

-- 20260602120000_contract_users_names.sql
-- Run only after the new application version has been stable in production.

ALTER TABLE users
  ADD CONSTRAINT users_full_name_present
  CHECK (full_name IS NOT NULL) NOT VALID;

ALTER TABLE users
  VALIDATE CONSTRAINT users_full_name_present;

ALTER TABLE users
  ALTER COLUMN full_name SET NOT NULL;

ALTER TABLE users
  DROP CONSTRAINT users_full_name_present;

-- Drop old columns in a later deploy, not in the same deploy that changes reads.
-- ALTER TABLE users DROP COLUMN name;

Der gefährlichste Irrtum lautet: Eine Down-Migration stellt alles wieder her. Gelöschte Spalten, überschriebene Werte und verlustbehaftete Typumwandlungen kommen nicht durch rückwärts ausgeführtes SQL zurück. Realistisch rollst du oft App-Version oder Feature Flag zurück. Datenbankseitig brauchst du Backup, Point-in-Time-Recovery oder einen Forward Fix.

prisma migrate resolve --rolled-back macht eine erfolgreiche Migration ebenfalls nicht rückgängig. Es löst den Historienstatus nach fehlgeschlagenen Migrationen. Claude Code sollte in jedem Plan App-Rollback, DB-Forward-Fix und Datenwiederherstellung trennen.

Typische Fehler Und Team-Prozess

Fehler eins: Ein Rename wird als Drop and Add erzeugt. Das ORM kennt deine Absicht nicht sicher, also muss das SQL gelesen werden. Fehler zwei: Schemaänderung und massive Datenänderung landen in einer Migration. Fehler drei: Shadow database wird überschätzt. Sie hilft in Entwicklung, aber simuliert keine Produktionsdaten, kein Bloat, keine Lock-Warteschlange und keinen Replikationslag. Fehler vier: Backup-Prüfung bleibt mündlich.

Schreibe DB-Regeln in CLAUDE.md: keine Column-Drops auf heißen Tabellen im selben PR, keine großen Backfills in Schema-Migrationen, Prisma-Migrationen mit --create-only für SQL-Review, offizielle Links im Review. Für Struktur passt CLAUDE.md Best Practices.

Bei umsatzkritischem SaaS stoppt eine schlechte Migration nicht nur Technik, sondern auch Billing, Signup und Support. Die ClaudeCodeLab Produkte enthalten wiederverwendbare Prompts und Checklisten, und Claude Code Training hilft beim Einbau in echte Repositories.

In praktischer Nutzung kam der größte Gewinn nicht von clevererem SQL, sondern von kleineren Einheiten. Ein Expand-PR, ein separates Backfill-Job und ein späterer Contract-PR machen Claude-Code-Ausgaben leichter reviewbar. Vor SQL zuerst Abbruchbedingungen und Recovery-Schritte schreiben zu lassen, reduziert Produktionsrisiko deutlich.

Fazit

Claude Code macht Produktionsmigrationen nicht automatisch sicher. Es wird stark, wenn das Team ein sicheres Modell vorgibt: expand/contract, gestuftes App-Deployment, kleine Backfills, CI, Staging-Probe, Feature Flags und klare Rollback-Grenzen.

Für die nächste Migration: Lass Claude Code zuerst die Risiken prüfen. Wenn der Plan hält, kann es SQL, Prisma-Migration, GitHub-Actions-Check und Backfill-Skript schreiben.

#Claude Code #database migration #Prisma #PostgreSQL #CI/CD
Kostenlos

Kostenloses PDF: Claude-Code-Cheatsheet

E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.

Wir schützen Ihre Daten und senden keinen Spam.

Masa

Über den Autor

Masa

Engineer für praktische Claude-Code-Workflows und Team-Einführung.