CommunityRecherche & Datenanalysegithub.com

PostgreSQL-Migrationen

PostgreSQL-Migrationen: gepaarte Up/Down-Dateien, moderne Typen und Constraints (IDENTITY, text mit CHECK, timestamptz, jsonb), Rückgängigmachen jeder Operation, Hinzufügen von Enum-Werten, nicht-blockierende Indizes auf Live-Tabellen via CREATE INDEX CONCURRENTLY, Anforderungen an den Migrator und Analyse von „Index vorhanden, aber Seq Scan“.

Was ist PostgreSQL-Migrationen?

Diese Skill-Definition beschreibt eine Methodik für PostgreSQL-Datenbankmigrationen, die auf dem Konzept gepaarter Up- und Down-Dateien basiert, wobei jede Migration vollständig rückgängig gemacht werden kann. Sie legt besonderen Wert auf die Verwendung moderner SQL-Typen wie `IDENTITY`-Spalten, `text` mit `CHECK`-Constraints anstelle von `varchar(n)`, `timestamptz` und `jsonb`. Für nicht-blockierende Indizes auf Tabellen mit aktivem Schreibzugriff wird `CREATE INDEX CONCURRENTLY` empfohlen, jedoch nur in einer separaten, transaktionslosen Migration mit einem einzigen Befehl pro Datei. Die Skill enthält detaillierte Anleitungen zum Rückgängigmachen aller gängigen Schemaänderungen, zur Behandlung von Enum-Werten (einschließlich der Limitierung, dass `ALTER TYPE ... ADD VALUE` erst nach dem Commit der Migration verwendet werden kann) und zur Fehlerbehebung, wenn ein gültiger Index vom Optimierer ignoriert wird (`Seq Scan` trotz vorhandenem Index). Abschließend werden die technischen Anforderungen an den Migrationsmanager definiert, insbesondere die Unterstützung für transaktionslose Migrationen, exklusive Sperren zur Verhinderung paralleler Ausführungen und die automatische Erstellung von Up/Down-Dateipaaren.

Funktioniert mit✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/scary-production/claude-plugins/tree/HEAD/plugins/go-postgres/skills/postgres-migrations

Installed? Explore more Recherche & Datenanalyse skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Was macht PostgreSQL-Migrationen?

Das Wichtigste. Jede Migration besteht aus einem Up/Down-Paar, und das Down-Skript macht das Up-Skript vollständig rückgängig. Jede Migration wird in einer eigenen Transaktion ausgeführt. Es gibt eine Ausnahme: Ein Index auf einer Tabelle, die bereits Daten enthält und in die geschrieben wird, wird mittels CREATE INDEX CONCURRENTLY in einer separaten Migration ohne Transaktion erstellt, ein Befehl pro Datei.

Die Überprüfung der Behauptungen erfolgt in CHECKS.md.

Dateiformat

migrations/
├── 20250524120000_create_users.up.sql
├── 20250524120000_create_users.down.sql
├── 20250525090000_add_users_phone.up.sql
└── 20250525090000_add_users_phone.down.sql
  • Das Präfix ist die UTC-Zeit im Format YYYYMMDDHHmmss, der Name ist eine kurze Beschreibung in snake_case.
  • *.up.sql wendet die Änderungen an, *.down.sql macht sie rückgängig. Beide Dateien sind erforderlich.
  • Eine bereits angewendete Migration darf nicht bearbeitet werden – schreibe stattdessen eine neue.

Typen und Constraints

StattdessenSchreibeWarum
bigserialbigint GENERATED ALWAYS AS IDENTITYSQL-Standard; verwende BY DEFAULT, wenn eigene IDs (Import) eingefügt werden müssen
varchar(n)text NOT NULL CHECK (length(x) <= n)Die Längenbeschränkung ist eine Datenregel, die sich leichter ändern lässt
timestamptimestamptzSpeichert einen Zeitpunkt, nicht „Uhrzeit an der Wand“
integer-Flagboolean
jsonjsonbBinäres Format, Indizes und Operatoren
uuid aus Erweiterunguuid DEFAULT gen_random_uuid()Seit PostgreSQL 13 integriert

Die Länge einer Textspalte sollte immer per CHECK eingeschränkt werden, außer bei bekanntermaßen langen Texten (Beschreibung, Kommentar). Ist die maximale Länge unklar, frage den Benutzer.

DatenEinschränkung
E-MailCHECK (length(email) <= 320)
NameCHECK (length(name) <= 255)
TelefonCHECK (length(phone) <= 50)
Slug, CodeCHECK (length(code) <= 100)
URLCHECK (length(url) <= 2048)
BeschreibungKeine Einschränkung oder CHECK (length(bio) <= 10000)

Tabellenvorlage:

CREATE TABLE users (
    id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name       text        NOT NULL CHECK (length(name) <= 255),
    email      text        NOT NULL CHECK (length(email) <= 320),
    is_active  boolean     NOT NULL DEFAULT true,
    metadata   jsonb,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now()
);

CREATE UNIQUE INDEX ix_users_email ON users (email);

Rückgängigmachen

updown
CREATE TABLE tDROP TABLE IF EXISTS t
ALTER TABLE t ADD COLUMN cALTER TABLE t DROP COLUMN IF EXISTS c
CREATE INDEX ixDROP INDEX IF EXISTS ix
CREATE INDEX CONCURRENTLY ixDROP INDEX CONCURRENTLY IF EXISTS ix – ebenfalls ohne Transaktion
CREATE TYPE t AS ENUM (...)DROP TYPE IF EXISTS t
ALTER TYPE t ADD VALUE 'v'Es gibt kein DROP VALUE in PostgreSQL – entweder Typ neu erstellen oder leeres Down-Skript
ALTER TABLE t ALTER COLUMN c SET NOT NULLALTER TABLE t ALTER COLUMN c DROP NOT NULL
ALTER TABLE t ADD CONSTRAINT cALTER TABLE t DROP CONSTRAINT IF EXISTS c

IF EXISTS im Down-Skript stellt sicher, dass das Rückgängigmachen idempotent ist.

Typische Änderungen

Spalte mit Standardwert. Bei konstantem DEFAULT wird die Tabelle nicht neu geschrieben (PostgreSQL 11+), der Befehl ist sofort ausgeführt. Bei volatilem Standardwert (gen_random_uuid(), clock_timestamp()) wird die Tabelle unter einer exklusiven Sperre vollständig neu geschrieben – füge bei einer großen Live-Tabelle die Spalte ohne Standardwert hinzu und befüllen sie in Batches.

ALTER TABLE users ADD COLUMN role text NOT NULL DEFAULT 'user' CHECK (length(role) <= 50);

Fremdschlüssel – erstelle sofort einen Index auf der referenzierenden Spalte: PostgreSQL erstellt ihn nicht automatisch, und ohne ihn werden sowohl das Löschen des Elterneintrags als auch Joins sequenziell durchlaufen.

CREATE TABLE orders (
    id      bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    user_id bigint NOT NULL REFERENCES users (id),
    total   numeric NOT NULL
);
CREATE INDEX ix_orders_user_id ON orders (user_id);

Enum-Wert hinzufügen. ALTER TYPE ... ADD VALUE funktioniert innerhalb einer Transaktion, der neue Wert kann jedoch erst nach dem Commit verwendet werden (55P04: unsafe use of new value). Wenn der Migrator die Migration in einer Transaktion ausführt, füge den Wert hinzu und verwende ihn (UPDATE, DEFAULT, CHECK) in verschiedenen Migrationen.

ALTER TYPE payment_method ADD VALUE 'bank_transfer';

Down: Es gibt kein DROP VALUE. Der Wert ist harmlos – lasse das Down-Skript mit einem Kommentar leer; falls nicht, erstelle den Typ neu (neuen Typ, ALTER COLUMN ... TYPE ... USING, lösche den alten).

Indizes auf Live-Tabellen

Die Tabelle enthält bereits Daten und es wird in sie geschrieben – der Index wird über CREATE INDEX CONCURRENTLY (CIC) erstellt. Ein normaler CREATE INDEX blockiert das Schreiben für die gesamte Dauer des Aufbaus. Ein Index zusammen mit einer neuen Tabelle in derselben Migration wird mit einem normalen CREATE INDEX erstellt. Die Datenbankgröße spielt keine Rolle: Eine hängende Transaktion zerstört den neuen Index bei jeder Größe (siehe unten).

Up:

CREATE INDEX CONCURRENTLY ix_orders_customer_id ON orders (customer_id);

Down:

DROP INDEX CONCURRENTLY IF EXISTS ix_orders_customer_id;
  • Separate Migration ohne Transaktion, ein Befehl pro Datei. Innerhalb eines Transaktionsblocks ist CIC verboten („cannot run inside a transaction block“). Mehrere Befehle, die in einer einzelnen Anfrage gesendet werden, führt PostgreSQL als implizite Transaktion aus, und CIC schlägt mit demselben Fehler fehl.
  • Wenn der Migrator keine Migration ohne Transaktion unterstützt, lege CIC nicht in eine normale Migration. Sage dem Benutzer, dass dieser Modus benötigt wird (Abschnitt „Anforderungen an den Migrator“), oder führe CIC separat aus und trage die Migration manuell ein.
  • CIC wartet auf alle Transaktionen mit altem Snapshot, selbst auf solche, die andere Tabellen lesen. Die Migration kann lange dauern.
  • Überprüfe nach dem Anwenden den Index. CIC ist nicht transaktional: Bei einem Fehler bleibt ein INVALID-Index zurück, die Migration selbst zeigt dies nicht an.
SELECT c.relname, i.indisvalid, i.indcheckxmin
FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid
WHERE c.relname = 'ix_orders_customer_id';
  • indisvalid = false – führe DROP INDEX CONCURRENTLY IF EXISTS aus und erstelle den Index neu, oder REINDEX INDEX CONCURRENTLY.
  • Wiederholung einer fehlgeschlagenen CIC-Migration. Ohne IF NOT EXISTS schlägt sie mit „relation already exists“ auf dem INVALID-Index fehl. Mit IF NOT EXISTS ist es schlimmer: Der Befehl überspringt stillschweigend den INVALID-Index, und die Migration gilt als „bestanden“. Verwende daher IF NOT EXISTS nicht in CIC: Entferne zuerst den ungültigen Index und wiederhole dann die Migration.

Index vorhanden, aber Seq Scan

Der Index ist gültig, aber der Optimierer verwendet ihn nicht – überprüfe indcheckxmin (siehe Abfrage oben). true: Abfragen verwenden den Index nicht, solange es Transaktionen gibt, die vor seiner Erstellung gestartet wurden. Grund – UPDATE: In der Tabelle sind HOT-Versionen-Ketten übrig, auf die der Index nicht verweist. Nur INSERT verursacht dies nicht. Erstelle den Index nicht neu und suche nicht nach der Ursache in der Statistik. Finde alte Transaktionen:

SELECT pid, xact_start, backend_xmin, state, left(query, 60) AS query
FROM pg_stat_activity
WHERE backend_xmin IS NOT NULL
ORDER BY xact_start;

Warte auf deren Abschluss oder beende sie (pg_terminate_backend(pid)) – jedoch nur nach Absprache mit dem Benutzer. Vor dem Release reproduzieren: Öffne in einer Sitzung eine repeatable-read-Transaktion mit Lesen der Tabelle, führe in einer anderen UPDATE-Zeilen und CREATE INDEX aus; indcheckxmin wird true.

Anforderungen an den Migrator

  • Jede Migration wird in einer eigenen Transaktion ausgeführt, zusammen mit dem Eintrag im Migrationslogbuch: Eine fehlgeschlagene Migration wird vollständig zurückgerollt, bereits angewendete bleiben erhalten.
  • Sperre gegen parallele Ausführung (z.B. advisory lock): Mehrere Replicas, die gleichzeitig starten, wenden nicht dieselbe Migration doppelt an.
  • Modus ohne Transaktion für einzelne Migrationen – Markierung in der Datei (z.B. erste Zeile -- migrate:no-transaction). Eine solche Datei wird als einzelner Befehl außerhalb der Transaktion gesendet. Nachteil: Das Anwenden und der Eintrag im Logbuch sind nicht mehr atomar. Fällt der Prozess dazwischen, schlägt die Wiederholung mit „already exists“ fehl; überprüfe indisvalid und trage bei gültigem Index die Migration manuell in das Logbuch ein.
  • Erstellung von Dateipaaren ohne Verbindung zur Datenbank.

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

scary-production/claude-plugins

Работа с PostgreSQL в Go через pgxpool (pgx v5) и avito-tech/go-transaction-manager: пул и устойчивый старт, структуры с тегами db и CollectRows, репозиторий через DefaultTrOrDB, транзакции через Do, propagation и savepoint, повтор транзакции при 40001/40P01/55P03, защита от SQL-инъекций, классификация ошибок и ON CONFLICT, batch и CopyFrom. Конкурентная запись и блокировки — скилл postgres-concurrency. PostgreSQL in Go via pgxpool and go-transaction-manager: pool, repositories, transactions, retries, errors, bulk loads.

scary-production/claude-plugins

Конкурентная запись в PostgreSQL без потерянных обновлений и write skew: условие в UPDATE, SELECT FOR NO KEY UPDATE против FOR UPDATE, exclusion-ограничения против пересечения интервалов, serializable и повтор при 40001/40P01/55P03, очередь задач на SKIP LOCKED, lock_timeout и NOWAIT, advisory-блокировки, разбор зависаний. Примеры — Go (pgx и go-transaction-manager), правила — про сам PostgreSQL. PostgreSQL concurrency: lost updates, write skew, row locks, exclusion constraints, isolation and retries, SKIP LOCKED queues, advisory locks.

scary-production/claude-plugins

Как найти, что тормозит в PostgreSQL, и доказать, что правка помогла. Профиль по pg_stat_statements, сравнение вариантов по числу страниц (BUFFERS), а не по секундам, разбор плана от места, где оценка строк впервые разошлась с фактом. N+1 из Go-цикла сворачиваем в один запрос. Правим условия, FK, статистику и память, а не боремся с планировщиком. Postgres query tuning: profile first, compare by buffers, fix row estimates, remove N+1.

scary-production/claude-plugins

Показывает стили строки состояния на примере или включает один из них (full, compact, bars, minimal) в настройках пользователя или проекта.

Verwandte Skills