8 Min

PostgreSQL-Schema vor der ersten Migration prüfen

Die PostgreSQL-Schemaverifizierung erkennt falsche Zuordnungen, schwache Constraints, fehlende Indizes und unsichere Änderungen, bevor die erste Migration Daten berührt.

PostgreSQL-Schema vor der ersten Migration prüfen

Ein KI-Builder kann gültiges PostgreSQL erzeugen und trotzdem die falsche Datenbank ableiten. Syntax ist der einfache Teil. Die gefährlichen Fehler wirken plausibel: Eine optionale Beziehung wird verpflichtend, eine Statuszeichenfolge erhält eine unvollständige CHECK-Constraint, eine Löschung läuft per Kaskade durch Datensätze, die erhalten bleiben sollten, oder eine Migration erstellt eine Tabelle neu und verliert dabei unbemerkt eine Spalte.

Die PostgreSQL-Schemaverifizierung muss deshalb Bedeutung, Migrationsverhalten und Wiederherstellung getrennt prüfen. Ich gebe ein abgeleitetes Schema erst frei, wenn es einen bekannten Datensatz, explizite Invarianten, repräsentative Abfragen, die Prüfung destruktiver Änderungen und eine Wiederherstellungsprobe überstanden hat. Fehlt nur einer dieser Punkte, ist die Migration weiterhin ein Vorschlag.

Die erste Migration verdient diese Prüfung, selbst wenn die Produktionsdatenbank leer ist. Früh gemachte Schemafehler verfestigen sich schnell, weil Anwendungscode, Seed-Daten, Berichte und spätere Migrationen von ihnen abhängen. Fünfzehn Minuten Prüfung vor dem ersten Lauf sind meist günstiger, als sechs Monate später zu erklären, warum zwei verschiedene Konzepte dieselbe nullable Textspalte teilen.

Ein abgeleitetes Schema ist eine nicht vertrauenswürdige Spezifikation

Behandeln Sie ein abgeleitetes Schema als Entwurf einer Spezifikation, nicht als ausführbare Wahrheit. Der Builder hat Prompts, Beispieloberflächen, importierte Datensätze oder generierten Anwendungscode gesehen. Er kennt nicht jede fachliche Ausnahme, Aufbewahrungsregel, jeden Massenimport, jede Supportkorrektur und jede fehlgeschlagene Zahlung, die die Datenbank irgendwann enthalten wird.

Trennen Sie zunächst drei Fragen, die Teams regelmäßig vermischen. Die Korrektheit des Schemas fragt, ob Tabellen und Constraints die Fachdomäne abbilden. Die Sicherheit der Migration fragt, ob die vorgeschlagenen Schritte bestehende Daten bewahren und die Datenbank während ihrer Ausführung nutzbar halten. Die Wiederherstellungsbereitschaft fragt, ob Sie nach einer teilweise ausgeführten oder inhaltlich falschen Änderung zu einem bekannten Zustand zurückkehren können. Das Bestehen einer Prüfung sagt kaum etwas über die anderen beiden aus.

Eine CREATE TABLE-Anweisung kann die gewünschte Zielstruktur beschreiben und trotzdem über unsichere Schritte dorthin gelangen. Angenommen, der Builder ändert customer_name text zu customer_id bigint. Der endgültige Fremdschlüssel kann sinnvoll sein, aber eine Migration, die die Namensspalte löscht, bevor historische Namen Kunden zugeordnet wurden, zerstört den einzigen Nachweis für diese Zuordnung. Die Schemaprüfung gibt das Ziel frei, die Migrationsprüfung untersucht den Weg dorthin.

Lesen Sie das vorgeschlagene Modell in Fachsprache laut vor. Sagen Sie, dass jede Rechnung genau einem rechtlichen Kunden gehört, statt zu sagen, dass invoices.customer_id auf customers.id verweist. Der erste Satz lädt zu hilfreichen Einwänden ein: Entwürfe können vor der Kundenauswahl existieren, importierte Rechnungen können sich auf archivierte Kunden beziehen, und bei der Ausstellung muss der Kundenname womöglich festgeschrieben werden. SQL-Vokabular kann solche Meinungsverschiedenheiten verdecken.

Ich verlange neben jeder abgeleiteten Tabelle eine Notiz zu den Annahmen. Darin sollte stehen, was eine Zeile bedeutet, wie sie identifiziert wird, wem sie gehört, ob sie ohne ihren scheinbaren Parent existieren darf und was Löschen bedeutet. Kann das Team diese Punkte nicht beantworten, hat der Builder eine Datenbank erraten, die das Team nicht entworfen hat.

Bekannte Datensätze decken falsche Tabellenzuordnungen auf

Ein bekannter Datensatz sollte Datensätze enthalten, die gezielt die fachliche Abdeckung sichern. Eine große Zufallsstichprobe wiederholt oft denselben einfachen Fall. Zehn sorgfältig ausgewählte Datensätze können mehr zeigen als zehntausend fast identische Happy-Path-Zeilen.

Erstellen Sie vor dem Ausführen von DDL eine Zuordnungsmatrix. Jede Zeile der Matrix sollte ein Quellkonzept bis zu seinem vorgeschlagenen Ziel verfolgen und die erwartete Anzahl oder den erwarteten Wert festhalten. Für eine Bestellanwendung könnte das Artefakt so aussehen:

Bekannte TatsacheVorgeschlagenes ZielErwartetes Ergebnis
Bestellung A hat zwei Positionenorders und order_itemsEine Bestellzeile und zwei Child-Zeilen
Bestellung B hat kein zugeordnetes Kontoorders.account_idEine Zeile mit NULL-Konto
Zwei Personen teilen eine E-Mail-Adressecontacts.emailBeide Zeilen bleiben erhalten, sofern Eindeutigkeit keine festgelegte Regel ist
Der Produktcode hat führende Nullenproducts.codeDer Textwert 00417 bleibt unverändert
Eine stornierte Bestellung behält ihre Belastungenorders und chargesBelastungszeilen bleiben nach der Stornierung erhalten

Damit fallen Fehler bei der Tabellenzuordnung auf, bevor Constraint-Details die Prüfung ablenken. KI-Builder normalisieren wiederholte Objekte oft zu separaten Tabellen, was meist sinnvoll ist. Wiederholung beweist jedoch keine Identität. Zwei Lieferadressen mit demselben Text können historische Momentaufnahmen sein und nicht Verweise auf eine bearbeitbare Adresszeile. Werden sie zusammengeführt, schreibt eine spätere Adressänderung die Vergangenheit um.

Auch der umgekehrte Fehler kommt vor. Ein Builder kann Kundenfelder in jede Bestellung kopieren, weil sie auf dem Bildschirm zusammen angezeigt werden. Einige Werte gehören zum Kunden, andere müssen als Momentaufnahme der Bestellung erhalten bleiben. Das richtige Design kann sowohl customer_id als auch Felder des ausgestellten Dokuments wie billing_name enthalten. Das als Duplizierung zu bezeichnen und eine Seite zu löschen, verliert entweder die aktuelle Identität oder die historische Wahrheit.

Laden Sie den bekannten Datensatz mit demselben Import- oder Seed-Pfad, den die Anwendung nutzen wird, in eine austauschbare Datenbank. Schreiben Sie dann Assertions zu Fakten, nicht nur zu Zeilenzahlen:

SELECT
    (SELECT count(*) FROM orders WHERE external_id = 'ORDER-A') AS order_a,
    (SELECT count(*) FROM order_items i
       JOIN orders o ON o.id = i.order_id
      WHERE o.external_id = 'ORDER-A') AS order_a_items,
    (SELECT account_id IS NULL FROM orders
      WHERE external_id = 'ORDER-B') AS order_b_unassigned;

Eine erfolgreiche Form sollte ausdrücklich festgelegt sein:

 order_a | order_a_items | order_b_unassigned
---------+---------------+---------------------
       1 |             2 | t

Akzeptieren Sie keine unerklärte Abweichung, nur weil die generierte Anwendung weiterhin angezeigt wird. Eine Oberfläche kann doppelte Parents, verlorene Child-Zeilen, abgeschnittene Codes und erfundene Standardwerte verstecken. Gleichen Sie jeden bewusst angelegten Testdatensatz ab, bevor Sie über den Produktions-Rollout sprechen.

Constraints müssen fachliche Wahrheiten abbilden

Eine Datenbank-Constraint sollte einen Zustand ablehnen, der immer ungültig ist, unabhängig davon, welcher Bildschirm, welche API, welcher Import oder welches Reparaturskript die Zeile schreibt. Hat eine Regel Ausnahmen oder hängt sie von veränderlichen externen Fakten ab, führt das Erzwingen in einer einfachen Constraint oft entweder zu blockierter Arbeit oder unehrlichen Daten.

Primärschlüssel identifizieren Zeilen, sie schaffen nicht automatisch eine sinnvolle fachliche Identität. Eine interne bigint-ID kann neben einer eindeutigen, mandantenbezogenen Bestellnummer bestehen. Sagt die Fachregel, dass Bestellnummern pro Mandant eindeutig sind, drückt UNIQUE (tenant_id, order_number) diese Regel aus. Eine globale Unique-Constraint würde zulässige Datensätze ablehnen, während gar keine Constraint bei Wiederholungsversuchen Mehrdeutigkeit erlauben würde.

CHECK-Constraints eignen sich für stabile Fakten einer Zeile wie quantity > 0 oder finished_at >= started_at. Das PostgreSQL-Handbuch erklärt, dass die Datenbank annimmt, ein CHECK-Ausdruck bleibe über die Lebensdauer der Constraint unveränderlich. Deshalb kann ein CHECK, der eine Funktion aufruft, deren Verhalten sich später ändert, dazu führen, dass alte Zeilen die scheinbare Regel verletzen. Verwenden Sie für unveränderliche Wahrheiten einen festen Ausdruck. Legen Sie veränderliche Richtlinien, etwa eine aktuell erlaubte und von Administratoren gesteuerte Menge, in einer referenzierten Tabelle oder im Anwendungsablauf ab.

Bei generierten Status-Constraints ist Skepsis angebracht. Ein Builder kann aktuelle Beispiele auswerten und Folgendes erzeugen:

status text NOT NULL
    CHECK (status IN ('draft', 'active', 'closed'))

Das stimmt nur, wenn dies die vollständigen dauerhaften Zustände sind. Fragen Sie nach fehlgeschlagenen, stornierten, ausgesetzten, importierten und unbekannten Legacy-Datensätzen. Wenn sich die Zustandsmaschine noch verändert, kann eine Lookup-Tabelle Ergänzungen sichtbar machen, ersetzt aber keine Prüfung der Übergänge. Dass eine Zeile closed enthalten darf, sagt nichts darüber aus, ob sie direkt von draft nach closed wechseln darf.

Setzen Sie Eindeutigkeit bewusst ein. PostgreSQL implementiert eine Unique-Constraint mit einem eindeutigen B-tree-Index, aber ein partieller eindeutiger Index drückt eine andere Regel aus. Bei Soft Deletion ist Eindeutigkeit häufig nur unter aktiven Zeilen nötig:

CREATE UNIQUE INDEX users_tenant_email_live_uq
    ON users (tenant_id, lower(email))
    WHERE deleted_at IS NULL;

Das ist nicht austauschbar mit UNIQUE (tenant_id, email, deleted_at). PostgreSQL behandelt NULL-Werte gemäß seinen Eindeutigkeitsregeln, und durch das Hinzufügen des Löschzeitstempels ändert sich die erzwungene Identität. Prüfen Sie die genauen Duplikatfälle mit Testdatensätzen, statt das Verhalten aus der Spaltenliste abzuleiten.

Nullability ist eine fachliche Entscheidung

Setzen Sie eine Spalte nur dann auf NOT NULL, wenn die Fachdomäne für jede zulässige Zeile einen Wert verlangt und jeder Schreibpfad diesen liefern kann. Das Design eines Bildschirms ist ein schwacher Beleg. Ein Pflichtfeld im aktuellen Formular sagt nichts über Importe, Entwürfe, vom System erzeugte Zeilen oder historische Datensätze aus.

Prüfen Sie vier Zustände getrennt: Die Quelle hat das Feld ausgelassen, die Quelle hat ausdrücklich null gesendet, die Quelle hat einen leeren Wert gesendet und die Quelle hat einen aussagekräftigen Wert geliefert. JSON-APIs, Formulare, CSV-Importe und PostgreSQL können diese Zustände unterschiedlich behandeln. Fasst die Anwendung alle vier vor dem Einfügen zusammen, sollte die Schemaprüfung diese Entscheidung sichtbar machen, statt so zu tun, als habe die Datenbank sie getroffen.

Standardwerte verdienen dieselbe Aufmerksamkeit. Ein Standardwert liefert einen Wert, wenn ein INSERT die Spalte auslässt. Er repariert kein explizites NULL und beweist nicht, dass der Wert wahr ist. country_code DEFAULT 'US' ist gefährlich, wenn ein unbekanntes Land möglich ist. Die Zeile enthält dann eine überzeugende Lüge, der Berichte und Compliance-Logik vertrauen könnten.

Eine häufig generierte Migration fügt eine Pflichtspalte in einer Anweisung hinzu:

ALTER TABLE customers
    ADD COLUMN account_type text NOT NULL DEFAULT 'standard';

Die Anweisung kann ausgeführt werden, doch jeder historische Kunde wird ohne Beleg zum Standardkunden. Eine sicherere Reihenfolge fügt die nullable Spalte hinzu, leitet Werte aus bekannten Daten ab, misst die ungelösten Zeilen, verhindert neue Auslassungen bei Schreibvorgängen der Anwendung und fügt erst dann NOT NULL hinzu, sofern die Fachdomäne das trägt. Bleibt unbekannt zulässig, behalten Sie NULL bei und legen fest, wie Abfragen und Oberflächen es darstellen.

PostgreSQL bietet für einige Constraints eine hilfreiche Trennung. Eine CHECK-Constraint oder ein Fremdschlüssel kann mit NOT VALID hinzugefügt werden. Dadurch werden nicht alle bestehenden Zeilen beim Erstellen geprüft, später kann mit VALIDATE CONSTRAINT validiert werden. Das Handbuch dokumentiert dies als Möglichkeit, den ersten Tabellenscan zu verschieben. Es ist keine Erlaubnis, alte Verstöße zu ignorieren: Neue Schreibvorgänge werden erzwungen, und der Validierungsschritt muss vor der Freigabe weiterhin bestehen.

Bevor Sie die Nullability verschärfen, führen Sie eine Verteilungsabfrage aus, die die tatsächlichen Kategorien zeigt:

SELECT
    count(*) AS total,
    count(*) FILTER (WHERE account_type IS NULL) AS nulls,
    count(*) FILTER (WHERE account_type = '') AS empty_strings,
    count(*) FILTER (WHERE account_type NOT IN
        ('standard', 'partner', 'internal')) AS unexpected
FROM customers;

Ein KI-generierter Standardwert kann diese Abfrage nach der Migration sauber aussehen lassen. Führen Sie sie auch vor dem Backfill aus und bewahren Sie das Ergebnis auf. Sonst verlieren Sie den Nachweis, um abgeleitete Werte von erfundenen zu unterscheiden.

Indizes sollten beobachtete Zugriffsmuster unterstützen

Auf geprüften Regeln aufbauen
Machen Sie aus geprüften Fachregeln per Chat eine Web-, Server- oder mobile Anwendung.

Geben Sie einen Index frei, wenn er eine bekannte Abfrage unterstützt, eine festgelegte Eindeutigkeitsregel erzwingt oder eine betriebliche Anforderung erfüllt. Jede Spalte zu indizieren, die wie eine Kennung aussieht, verschwendet Speicher und Schreibarbeit. Fehlt ein wichtiger zusammengesetzter Index, kann eine gewöhnliche Listenseite zu einem immer größeren Scan werden.

Beginnen Sie mit den Abfragen, die die generierte Anwendung tatsächlich stellt. Erfassen Sie Filterspalten, Mandantengrenze, Join-Spalten, Sortierung und erwartete Ergebnisgröße. Für eine Seite mit aktuellen Bestellungen ist diese Abfrageform wichtiger als das Tabellendiagramm:

SELECT id, order_number, status, created_at
FROM orders
WHERE tenant_id = $1
  AND status = $2
ORDER BY created_at DESC
LIMIT 50;

Ein Index allein auf tenant_id muss möglicherweise weiterhin viele Mandantenzeilen untersuchen und sortieren. Ein Index auf (tenant_id, status, created_at DESC) passt genauer zu diesem Zugriffsmuster. Die Spaltenreihenfolge ist kein Beliebtheitswettbewerb. Sie folgt Gleichheitsbedingungen, Bereichsbedingungen, Sortierung und Selektivität in der tatsächlichen Abfrage.

Führen Sie EXPLAIN (ANALYZE, BUFFERS) mit repräsentativen Daten aus, behandeln Sie aber keinen kleinen Testdatensatz als Leistungsbeweis. PostgreSQL kann bei einer winzigen Tabelle zu Recht einen sequenziellen Scan bevorzugen. Die Verifizierung sollte bestätigen, dass der vorgesehene Index vorhanden ist und eine Probe in Produktionsgröße dem Planner eine realistische Wahl ermöglicht. Deaktivieren Sie nie sequenzielle Scans, um für die Freigabe einen Index-Scan zu erzwingen.

Fremdschlüssel bringen eine weitere regelmäßige Überraschung mit sich: PostgreSQL indiziert die referenzierten Primär- oder Unique-Spalten, erstellt aber nicht automatisch einen Index auf den referenzierenden Child-Spalten. Beim Löschen oder Aktualisieren eines Parents kann die Child-Tabelle deshalb nach Referenzen durchsucht werden. Auch Joins vom Child zum Parent können diesen Child-Index benötigen. Prüfen Sie jede Beziehung anhand der erwarteten Lesevorgänge und Änderungen am Parent.

Lehnen Sie doppelte und ungenutzte Indizes im ersten Vorschlag ab. (tenant_id, status) kann redundant sein, wenn bereits ein geeigneter Index (tenant_id, status, created_at) existiert, obwohl Details der Arbeitslast diese Einschätzung ändern können. Vergleichen Sie Definitionen, nicht Namen. KI-Builder erstellen oft pro Funktion einen Index und übersehen, dass mehrere Funktionen dieselben führenden Spalten angefordert haben.

Bei bestehenden stark genutzten Datenbanken sollten Sie bedenken, dass CREATE INDEX CONCURRENTLY nicht innerhalb eines Transaktionsblocks ausgeführt werden kann, mehr Arbeit verursacht und nach einem Fehler einen ungültigen Index hinterlassen kann. Das PostgreSQL-Handbuch beschreibt diese betrieblichen Unterschiede. Ein Migrationsframework, das jede Migration in eine Transaktion einschließt, braucht eine ausdrückliche Ausnahme und einen Bereinigungsablauf, nicht nur einen hoffnungsvollen Austausch eines Schlüsselworts.

Fremdschlüssel brauchen Besitzverhältnisse und Löschregeln

Ein Fremdschlüssel ist erst korrekt, nachdem das Team entschieden hat, ob die Beziehung Besitz, Referenz, optionalen Kontext oder historische Zuordnung ausdrückt. Ähnlich aussehende Spalten können entgegengesetztes Löschverhalten brauchen.

Betrachten Sie projects.owner_user_id, invoices.customer_id und audit_events.actor_user_id. Ein Projekt kann den Besitzer wechseln. Eine Rechnung muss möglicherweise die Schließung eines Kundenkontos überstehen. Ein Audit-Ereignis kann die frühere Kennung des Akteurs bewahren, selbst wenn Identitätsdaten entfernt wurden. Für alle drei ON DELETE CASCADE anzuwenden, weil sie auf users verweisen, würde eine destruktive Fiktion festschreiben.

Verwenden Sie CASCADE, wenn das Child ohne den Parent keine Bedeutung hat und das Löschen des Parents tatsächlich das Löschen des gesamten Aggregats bedeutet. Bestellpositionen passen oft dazu. Zahlungsdatensätze, ausgestellte Dokumente, Importe, Protokolle und Moderationsnachweise häufig nicht. Für sie können Ablehnung, Archivierung, kontrollierte Anonymisierung oder eine nullable Referenz zusammen mit erhaltenen Momentaufnahmen besser passen.

Auch SET NULL verlangt eine semantische Prüfung. Es bewahrt die Child-Zeile, löscht aber die direkte Beziehung. Müssen Mitarbeitende später erklären, welches Konto einen Bericht erstellt hat, reicht eine null-Referenz womöglich nicht aus. Ein nicht identifizierendes historisches Token oder eine Momentaufnahme kann Rechenschaft ermöglichen, ohne alle personenbezogenen Daten zu behalten. Die genaue Aufbewahrungsentscheidung gehört jedoch zur Produktrichtlinie, nicht zu einer KI-Vermutung.

Prüfen Sie die Kardinalität in beide Richtungen. Ein Builder kann Eins-zu-eins modellieren, indem er einen Fremdschlüssel ohne Unique-Constraint setzt und damit unbemerkt viele Child-Zeilen zulässt. Er kann auch Eindeutigkeit erzwingen, obwohl die Historie mehrere Versionen braucht. Schreiben Sie Testfälle für einen Parent mit null, einem und mehreren Children und legen Sie dann fest, welche INSERTs bestehen sollen.

Deferrable Constraints brauchen einen konkreten Grund. Sie können helfen, wenn eine Transaktion vorübergehend die Referenzreihenfolge verletzen oder voneinander abhängige Zeilen aktualisieren muss. Jeden Fremdschlüssel standardmäßig aufzuschieben, verlagert Fehler jedoch auf den Commit und erschwert ihre Zuordnung. Behalten Sie die sofortige Durchsetzung bei, sofern keine reale Transaktionsfolge einen Aufschub verlangt.

Prüfen Sie nach dem Anwenden der Migration auf der Probedatenbank den Katalog:

SELECT
    conname,
    contype,
    convalidated,
    pg_get_constraintdef(oid) AS definition
FROM pg_constraint
WHERE conrelid = 'public.orders'::regclass
ORDER BY conname;

Repräsentative Ausgabe hat etwa diese Form:

       conname        | contype | convalidated | definition
----------------------+---------+--------------+---------------------------------------
 orders_pkey          | p       | t            | PRIMARY KEY (id)
 orders_customer_fk   | f       | t            | FOREIGN KEY (customer_id) REFERENCES customers(id)
 orders_total_check   | c       | t            | CHECK ((total_cents >= 0))

Vergleichen Sie die zurückgegebenen Definitionen mit den freigegebenen Besitzregeln. Ein erfolgreicher Migrationslauf zeigt keine fehlende Aktion, unerwartete Aufschiebung oder nicht validierte Constraint.

Destruktive Änderungen verstecken sich in vernünftig wirkendem SQL

Aus der Bedeutung der Fachdomäne entwickeln
Erstellen Sie die Anwendung rund um die Fachsprache, auf die sich Ihre Prüfer bereits geeinigt haben.

Prüfen Sie die Migration als Datentransformation, denn ordentlich wirkendes DDL kann Bedeutung verwerfen, ohne ein offensichtliches DROP TABLE zu verwenden. Suchen Sie zuerst nach direkter Zerstörung, untersuchen Sie dann Casts, Backfills, Umschreibungen, Umbenennungen und das Ersetzen von Constraints.

Eine Umbenennung und ein Löschen unterscheiden sich betrieblich, selbst wenn das Zielschema gleich aussieht. Wird surname zu family_name, bewahrt eine Umbenennung Daten und Abhängigkeiten zuverlässiger. Die alte Spalte zu löschen und die neue hinzuzufügen ergibt dasselbe Diagramm, leert aber jeden Wert. Generierte Migrationen leiten oft den Endzustand ab, ohne Kontinuität zu verstehen.

Typänderungen brauchen Beispielkonvertierungen und Ablehnungsfälle. Die Umwandlung von Textkennungen in ganze Zahlen kann führende Nullen entfernen oder gemischte Kennungen ablehnen. Eine geringere numerische Genauigkeit kann Werte runden. Die Konvertierung von Zeitstempeln braucht eine festgelegte Zeitzonenannahme. Testen Sie den tatsächlichen USING-Ausdruck mit Mindestwert, Höchstwert, null, fehlerhaften und historisch ungewöhnlichen Werten, bevor Sie die Spalte ändern.

Behandeln Sie diese Vorgänge als zustimmungspflichtig: das Löschen einer Tabelle oder Spalte, eine Typänderung über einen verlustbehafteten Cast, das Ersetzen einer gefüllten Spalte, das Hinzufügen von CASCADE, das Setzen von NOT NULL nach einem generierten Backfill und das Neuaufbauen der Eindeutigkeit mit anderen Spalten. Prüfen Sie auch rohes SQL in generierten Funktionen oder Migrations-Callbacks. Textsuche ist ein erster Filter, nicht die gesamte Prüfung.

Ein Fehlermuster tritt immer wieder auf. Die bekannten Daten enthalten Kontakte mit optionalen Unternehmen, doch der Beispielbildschirm zeigt nur Geschäftskontakte. Der Builder macht contacts.company_id zu NOT NULL und fügt für nicht zugeordnete Zeilen ein generiertes Unternehmen namens Unknown ein. Die Migration besteht, die Zählungen stimmen überein und jeder Fremdschlüssel wird validiert. Die Daten sind dennoch falsch: Einzelkontakte scheinen nun zu einem Unternehmen zu gehören, Berichte gruppieren unverbundene Personen zusammen und das Löschen des Platzhalters kann auf echte Kontakte übergreifen.

Die Lösung ist nicht ein weiterer Standardwert. Stellen Sie den Quellzustand wieder her, machen Sie die Beziehung nullable, migrieren Sie nur Zuordnungen mit belegter Grundlage und fügen Sie eine Assertion hinzu, dass die nicht zugeordnete Menge den bekannten Einzelkontakten entspricht. Deshalb müssen semantische Testdatensätze erwartete Beziehungen erfassen, nicht nur erwartete Zeilenzahlen.

Schema-Diff-Tools helfen, doch ich rate davon ab, allein auf Basis eines Diffs freizugeben. Die Empfehlung ist beliebt, weil ein Diff kompakt und leicht zu prüfen ist. Als einziges Gate ist sie falsch, weil ein Diff strukturelle Änderungen zeigt, nicht aber die Herkunft von Backfill-Werten, Transaktionsgrenzen, Sperrverhalten oder die Wahrheit nach der Migration.

Eine Probe muss Ergebnisse und Fehlerverhalten nachweisen

Führen Sie die vollständige Migration auf einer austauschbaren Wiederherstellung des bekannten Datensatzes aus. Testen Sie anschließend sowohl das beabsichtigte Ergebnis als auch einen unterbrochenen oder abgelehnten Ablauf. Eine frische leere Datenbank hilft, Fehler in der Reihenfolge zu finden, kann aber keine verlustbehaftete Konvertierung, ungültige historische Zeilen oder langsame Validierung aufdecken.

Verwenden Sie diese Reihenfolge als Release-Artefakt:

  1. Stellen Sie den Datensatz vor der Änderung in einer isolierten Datenbank wieder her und erfassen Sie Zeilenzahlen sowie semantische Assertions.
  2. Erfassen Sie das aktuelle Schema, wenden Sie das exakte Migrationsartefakt an und speichern Sie alle Ausgaben samt Dauer und Transaktionsgrenzen.
  3. Führen Sie Katalogprüfungen, Zuordnungs-Assertions, Constraint-Ablehnungstests und repräsentative Anwendungsabfragen aus.
  4. Vergleichen Sie wichtige Werte mit den dokumentierten Erwartungen, einschließlich nicht zugeordneter und null-Kategorien.
  5. Führen Sie die dokumentierte Wiederherstellungsmethode aus und wiederholen Sie dann die Assertions vor der Migration auf der wiederhergestellten Datenbank.

Erstellen Sie vor und nach der Änderung einen reinen Schema-Dump:

pg_dump --schema-only --no-owner --no-privileges \
  --dbname "$DATABASE_URL" > schema.sql

Prüfen Sie Tabellen, Sequenzen, Indizes, Constraints, Funktionen, Trigger, Erweiterungen und für die Anwendung relevante Berechtigungen. Ein ORM-Modell-Diff kann Datenbankobjekte auslassen, die die Anwendung nicht modelliert, insbesondere Trigger, Ausdrucksindizes, partielle Indizes und manuell installierte Funktionen.

Fügen Sie negative Tests hinzu, die belegen, dass Constraints ungültige Zustände ablehnen. Eine Testtransaktion kann einen ungültigen INSERT versuchen und unabhängig vom Ergebnis zurückrollen:

BEGIN;

INSERT INTO order_items (order_id, quantity, unit_price_cents)
VALUES (1001, 0, 2500);

ROLLBACK;

Die erwartete Ausgabe sollte die verletzte Constraint nennen, zum Beispiel:

ERROR:  new row for relation "order_items" violates check constraint "order_items_quantity_check"
DETAIL:  Failing row contains (..., 0, 2500, ...).

Vergleichen Sie nicht den vollständigen Fehlertext in jeder Umgebung, da Details abweichen können. Prüfen Sie in automatisierten Tests SQLSTATE oder die Identität der Constraint und bewahren Sie eine lesbare Ausgabe für die prüfende Person auf.

Messen Sie Sperren und Dauer mit einem Datensatz, der der vorgesehenen Bereitstellung groß genug ähnelt. Ein Vorgang, der bei fünfzig Zeilen sofort fertig ist, kann Schreibvorgänge blockieren, wenn er Millionen validiert. Bei einer anfänglich leeren Produktionsdatenbank ist das unmittelbare Risiko geringer, doch die Probe testet weiterhin importierte Seed-Daten und schafft eine Ausgangsbasis für spätere Änderungen.

Wiederherstellung braucht mehr als eine Rückwärtsmigration

Wiederherstellung proben
Nutzen Sie Snapshots und Rollback als Teil eines erprobten Wiederherstellungswegs für generierte Anwendungen.

Eine Wiederherstellung ist nur glaubwürdig, wenn sie Daten und Anwendungskompatibilität innerhalb der Zeit wiederherstellt, die der Dienst verkraften kann. Eine Rückwärtsmigration, die gelöschte Spalten erneut anlegt, stellt deren frühere Werte nicht wieder her.

Wählen Sie vor der Ausführung die Einheit der Wiederherstellung. Für eine leere erste Datenbank kann das Löschen und Neuerstellen der Datenbank akzeptabel sein, sofern noch keine Nutzerschreibvorgänge begonnen haben. Sobald echte Schreibvorgänge existieren, kann die Wiederherstellung einen Datenbanksnapshot, ein logisches Backup, beibehaltene alte Spalten oder eine Vorwärtsreparatur erfordern. Die richtige Methode hängt davon ab, wie viele neue Daten während und nach der Migration eintreffen können.

Testen Sie Wiederherstellungsbefehle und Zugangsdaten, bevor Sie sich auf sie verlassen. Ein Backup, das existiert, aber vom Deployment-Verantwortlichen nicht wiederhergestellt werden kann, ist kein Wiederherstellungsplan. Stellen Sie es in einer separaten Datenbank wieder her, prüfen Sie Eigentümerschaft und Erweiterungen und führen Sie dann dieselben bekannten Assertions wie vor der Migration aus.

Snapshots und Transaktions-Rollback lösen unterschiedliche Fehler. Eine Transaktion kann Anweisungen rückgängig machen, wenn eine Migration vor dem Commit fehlschlägt, sofern alle Vorgänge an dieser Transaktion teilnehmen. Ein Snapshot kann die gesamte Datenbank in einen früheren Zustand zurückversetzen, kann dabei aber zulässige Schreibvorgänge nach dem Snapshot verwerfen. Keiner der beiden Mechanismen gleicht diese Schreibvorgänge automatisch ab.

Bevorzugen Sie additive Änderungen, solange Unsicherheit besteht. Fügen Sie die neue Spalte oder Tabelle hinzu, kopieren Sie Daten nach messbaren Regeln, lassen Sie bei Bedarf während eines kontrollierten Zeitraums beide Codepfade laufen und entfernen Sie die alte Struktur erst nach der Verifizierung. Dieser Expand-and-Contract-Ansatz kostet zusätzliche Arbeit, bewahrt aber Nachweise. Eine umbenannte alte Spalte einen Release lang zu behalten, ist oft günstiger, als sie aus Protokollen zu rekonstruieren.

Schreiben Sie Wiederherstellungsauslöser im Voraus auf. Dazu zählen jede fehlgeschlagene semantische Assertion, unerwartete nicht zugeordnete Datensätze, eine ungültige Constraint, eine Migration außerhalb ihres genehmigten Sperrfensters oder Anwendungsfehler durch Versionsinkompatibilität. Die verantwortliche Person sollte die Entscheidung nicht erst erfinden müssen, während Nutzer warten.

Dokumentieren Sie den Zeitpunkt, nach dem die Wiederherstellung der alten Datenbank auch die Wiederherstellung der alten Anwendung erfordert. Eine neue Anwendung kann von einer neuen Spalte abhängen, während eine alte Anwendung einen neuen Enum-Wert ablehnen oder die alte Struktur schreiben kann. Datenbank- und Anwendungswiederherstellung müssen kompatible Versionen verwenden.

Freigabe braucht Nachweise, nicht Zuversicht

Geben Sie die erste Migration erst frei, wenn eine andere Person anhand der abgelegten Artefakte nachvollziehen kann, warum sie sicher ist. Zuversicht aus einer sauberen Codeprüfung oder einer polierten generierten Oberfläche übersteht die erste unerklärte Datenabweichung nicht.

Der Freigabenachweis sollte die abgeleiteten Annahmen, die Tabellenzuordnungsmatrix, die Identität des bekannten Datensatzes, das Schema-Diff, die exakte Migration, Validierungsabfragen und Ergebnisse, Tests abgelehnter Eingaben, die Begründung für Indizes, Rechtfertigungen destruktiver Vorgänge und den getesteten Wiederherstellungsablauf enthalten. Nennen Sie die prüfende Person und behandeln Sie ungelöste Entscheidungen als Blocker, statt sie im Chatverlauf zu vergraben.

Wenn eine Anwendung in Koder.ai generiert wird, nutzen Sie den Planungsmodus, um diese Schemaentscheidungen vor der Migration festzuhalten. Exportieren Sie den Quellcode zur Prüfung und behandeln Sie Snapshots und Rollback als Wiederherstellungswerkzeuge, die trotzdem mit dem bekannten Datensatz geprobt werden müssen.

Lassen Sie den Builder seine eigene Ableitung nicht durch erneutes Generieren von Code freigeben, bevor die Tests bestehen. Dieser Kreislauf kann die Anwendung an ein fehlerhaftes Schema anpassen, statt das Modell zu korrigieren. Ein Mensch muss entscheiden, ob die Datenbank zur Fachdomäne passt, besonders bei Identität, Löschung, Aufbewahrung und unbekannten Werten.

Die abschließende Freigabeabfrage sollte langweilig sein. Jede bekannte Tatsache führt zu einem erwarteten Ergebnis, jede Constraint lehnt das vorgesehene Gegenbeispiel ab, jeder destruktive Vorgang hat einen Grund und die Wiederherstellung reproduziert die Assertions vor der Migration. Wenn die Nachweise eine überzeugende Erklärung brauchen, um eine Abweichung zu entschuldigen, stoppen Sie die Migration. PostgreSQL wird das Schema präzise erzwingen, auch die Teile, die der Builder falsch erraten hat.

FAQ

Was sollte ich in einem KI-generierten PostgreSQL-Schema prüfen?

Prüfen Sie das generierte DDL, die Migrationsschritte und die Annahmen dahinter. Ein korrektes Zielschema kann dennoch über eine Migration entstehen, die Daten löscht, Schreibvorgänge blockiert oder irreführende Standardwerte erfindet.

Wie groß sollte ein Datensatz zur Schemaverifizierung sein?

Verwenden Sie einen kleinen Datensatz mit normalen Zeilen, Grenzwerten, fehlenden Beziehungen, Duplikaten, NULL-Werten, leeren Zeichenfolgen und historischen Sonderfällen. Es geht nicht um Datenmenge, sondern darum, die Annahmen des Builders zu widerlegen, bevor Produktionsdaten es tun.

Beweist eine erfolgreich getestete Migration, dass das Schema sicher ist?

Nein. Eine erfolgreiche Migration zeigt nur, dass PostgreSQL die Anweisungen für diesen Datenbankzustand akzeptiert hat. Sie beweist weder, dass Tabellenzuordnungen stimmen, Daten ihre Bedeutung behalten haben, Indizes echte Abfragen unterstützen noch dass die Wiederherstellung funktioniert.

Wann sollte eine PostgreSQL-Spalte NOT NULL sein?

Ein Feld sollte nur dann NOT NULL sein, wenn jeder zulässige Datensatz einen Wert hat und die Anwendung diesen Wert in jedem Schreibpfad liefern kann. Verwenden Sie keinen erfundenen Standardwert nur um die Constraint zu erfüllen, denn so wird sichtbare fehlende Information durch glaubwürdig falsche Daten ersetzt.

Sollte ich eine Unique-Constraint oder einen eindeutigen Index verwenden?

Eine Unique-Constraint drückt eine Regel aus, auf die andere Datenbankobjekte verweisen können, und PostgreSQL stützt sie mit einem Index ab. Ein eindeutiger Index eignet sich, wenn Eindeutigkeit nur für ausgewählte Zeilen oder Ausdrücke gilt, etwa für nicht gelöschte Datensätze oder normalisierte E-Mail-Adressen.

Erstellen PostgreSQL-Fremdschlüssel automatisch Indizes?

Indizieren Sie Spalten, mit denen Elternzeilen gefunden, häufige Abfragen gefiltert, große Tabellen verknüpft oder Eindeutigkeit erzwungen wird. PostgreSQL indiziert die referenzierende Seite eines Fremdschlüssels nicht automatisch. Prüfen Sie Child-Spalten daher separat, statt anzunehmen, die Constraint habe das erledigt.

Wann ist ON DELETE CASCADE sicher?

Verwenden Sie CASCADE nur, wenn die Child-Zeile keine eigenständige Bedeutung mehr hat, sobald ihre Parent-Zeile verschwindet. Wenn das Löschen eine fachliche Entscheidung ist oder die Child-Zeile ein Beleg wie eine Rechnung oder ein Audit-Ereignis ist, lehnen Sie die Löschung ab oder verarbeiten Sie sie ausdrücklich.

Wie erkenne ich destruktive Änderungen in einer Migration?

Behandeln Sie jedes DROP, jeden verlustbehafteten Cast, jedes Umschreiben einer Tabelle, jede neue Pflichtspalte und jede ersetzte Constraint als potenziell destruktiv. Durchsuchen Sie den Migrationstext, prüfen Sie aber auch generierte Funktionen und rohes SQL, denn darin kann sich zerstörerisches Verhalten verbergen.

Wie teste ich die Wiederherstellung nach einer Migration am sichersten?

Stellen Sie die Datenbank vor der Änderung an einem separaten Ort wieder her, führen Sie dort die Migration aus, lassen Sie semantische Prüfungen laufen und vergleichen Sie die Ergebnisse mit dokumentierten Erwartungen. Nur die Rückwärtsmigration zu testen übersieht verlorene Daten und kann falsche Sicherheit vermitteln.

Welche Nachweise sollte ich nach der Freigabe eines Schemas aufbewahren?

Bewahren Sie generiertes DDL, Migrationstext, Schema-Diff, Prüfungsabfragen und ihre Ergebnisse, den Wiederherstellungsablauf sowie die Identität des Prüfers zusammen auf. Diese Unterlagen erklären, was freigegeben wurde, und helfen bei der nächsten Migrationsprüfung, geänderte Annahmen zu erkennen.

Related posts