Lär dig Claude Code-promptar för säkra expand-contract PostgreSQL-migrationer, backfills och rollback-planer, samt vad du ska verifiera i staging innan release.

En PostgreSQL-schemaändring ser enkel ut tills den möter riktig trafik och verklig data. Den riskfyllda delen är vanligtvis inte SQL:en i sig. Det blir farligt när din appkod, databastillstånd och deployment-timing slutar matcha.
De flesta fel är praktiska och smärtsamma: en deploy bryter eftersom gammal kod når en ny kolumn, en migration låser en het tabell och timeouter skjuter i höjden, eller en "snabb" ändring tyst tar bort eller skriver om data. Även när inget kraschar kan du släppa subtila buggar som felaktiga defaultvärden, brutna constraints eller index som aldrig hann byggas klart.
AI-genererade migrationer lägger till ytterligare ett risklager. Verktyg kan producera giltig SQL som ändå är osäker för din arbetsbelastning, volym eller releaseprocess. De kan också gissa tabellnamn, missa långkörande lås eller vifta bort rollback eftersom down-migrationer är svåra. Om du använder Claude Code för migrationer behöver du skyddsräcken och konkret kontext.
När det här inlägget säger att en ändring är "säker" betyder det tre saker:
Målet är att migrationer ska bli rutinjobb: förutsägbara, testbara och tråkiga.
Börja med några icke-förhandlingsbara regler. De håller modellen fokuserad och hindrar dig från att leverera en ändring som bara fungerar på din laptop.
Dela upp arbetet i små steg. En schemaändring, en data-backfill, en appändring och ett städrikt är olika risker. Att slå ihop dem gör det svårare att se vad som gick sönder och svårare att rulla tillbaka.
Föredra additiva ändringar framför destruktiva. Att lägga till en kolumn, index eller tabell är vanligtvis låg risk. Att byta namn eller ta bort objekt är där driftstopp händer. Gör den säkra delen först, flytta appen och ta bara bort det gamla när du är säker på att det inte används.
Gör så att appen tål båda formerna under en tid. Koden bör kunna läsa antingen den gamla kolumnen eller den nya under rollout. Det undviker det vanliga racet där vissa servrar kör ny kod medan databasen fortfarande är gammal (eller tvärtom).
Behandla migrationer som produktionskod, inte ett snabbt skript. Även om du bygger med en plattform som Koder.ai (Go-backend med PostgreSQL, plus React- eller Flutter-klienter) delas databasen av allt. Misstag är dyra.
Om du vill ha en kompakt uppsättning regler att lägga överst i varje SQL-begäran, använd något i stil med:
Ett praktiskt exempel: istället för att byta namn på en kolumn som din app är beroende av, lägg till den nya kolumnen, backfilla den långsamt, deploya kod som läser ny och sedan gammal, och ta först bort den gamla kolumnen när du är säker på att den inte används.
Claude kan skriva hyfsad SQL från en vag förfrågan, men säkra migrationer behöver kontext. Behandla din prompt som ett mini designbrief: visa vad som finns, förklara vad som inte får gå sönder och definiera vad "säkert" betyder för din rollout.
Börja med att klistra in bara de databasfakta som spelar roll. Inkludera tabellens definition plus relevanta index och constraints (primärnycklar, unika constraints, foreign keys, check-constraints, triggers). Om relaterade tabeller är inblandade, inkludera även dessa utdrag. Ett litet, korrekt utdrag hindrar modellen från att gissa namn eller missa en viktig constraint.
Lägg till verklig skala. Radantal, tabellstorlek, skrivfrekvens och peak-trafik bör påverka planen. "200M rader och 1k writes/sec" är en annan migration än "20k rader och mest reads." Inkludera också din Postgres-version och hur migrationer körs i ditt system (en transaktion vs flera steg).
Beskriv hur applikationen använder datan: viktiga reads, writes och bakgrundsjobb. Exempel: "API läser per email", "workers uppdaterar status" eller "rapporter skannar på created_at." Detta avgör om du behöver expand/contract, feature flags och hur säker en backfill blir.
Var tydlig med constraints och leverabler. En enkel struktur fungerar bra:
Att be om både SQL och en körplan tvingar modellen att tänka på ordning, risker och vad som ska kontrolleras innan du släpper.
Expand/contract-mönstret ändrar en PostgreSQL-databas utan att bryta appen medan ändringen pågår. Istället för en enda riskfylld switch låter du databasen stödja både gamla och nya former under en period.
Tänk på det som: lägg till nya saker säkert (expand), flytta trafik och data gradvis, och ta först bort det gamla när allt använder den nya vägen. Detta är särskilt användbart vid AI-assisterat arbete eftersom det tvingar fram en plan för mellanskedet.
En praktisk flöde ser ut så här:
Använd detta mönster när användare fortfarande kan köra gamla appversioner samtidigt som databasen ändras. Det inkluderar multi-instans-utrullningar, mobilappar som uppdateras långsamt eller alla releaser där en migration kan ta minuter eller timmar.
En användbar taktik är att planera för två releaser. Release 1 gör expand plus kompatibilitet så inget går sönder om backfill inte är klar. Release 2 gör contract först efter att du bekräftat att ny kod och ny data är på plats.
Kopiera denna mall och fyll i hakparenteserna. Den pressar Claude Code att producera SQL du kan köra, kontroller för att bevisa att det fungerade, och en rollback-plan som du faktiskt kan följa.
You are helping me plan a PostgreSQL expand-contract migration.
Context
- App: [what the feature does, who uses it]
- Database: PostgreSQL [version if known]
- Table sizes: [rough row counts], write rate: [low/medium/high]
- Zero/near-zero downtime required: [yes/no]
Goal
- Change: [describe the schema change]
- Current schema (relevant parts):
[paste CREATE TABLE or \d output]
- How the app will change (expand phase and contract phase):
- Expand: [new columns/indexes/triggers, dual-write, read preference]
- Contract: [when/how we stop writing old fields and remove them]
Hard safety requirements
- Prefer lock-safe operations. Avoid full table rewrites on large tables when possible.
- If any step can block writes, call it out explicitly and suggest alternatives.
- Use small, reversible steps. No “big bang” changes.
Deliverables
1) UP migration SQL (expand)
- Use clear comments.
- If you propose indexes, tell me if they should be created CONCURRENTLY.
- If you propose constraints, tell me whether to add them NOT VALID then VALIDATE.
2) Verification queries
- Queries to confirm the new schema exists.
- Queries to confirm data is being written to both old and new structures (if dual-write).
- Queries to estimate whether the change caused bloat/slow queries/locks.
3) Rollback plan (realistic)
- DOWN migration SQL (only if it is truly safe).
- If down is not safe, write a rollback runbook:
- how to stop the app change
- how to switch reads back
- what data might be lost or need re-backfill
4) Runbook notes
- Exact order of operations (including app deploy steps).
- What to monitor during the run (errors, latency, deadlocks, lock waits).
- “Stop/continue” checkpoints.
Output format
- Separate sections titled: UP.sql, VERIFY.sql, DOWN.sql (or ROLLBACK.md), RUNBOOK.md
Två extra rader som hjälper i praktiken:
RISK: blocks writes, plus när det bör köras (off-peak vs anytime).Små schemaändringar kan ändå göra ont om de tar långa lås, skriver om stora tabeller eller misslyckas halvvägs. När du använder Claude Code för migrationer, be om SQL som undviker omskrivningar och håller appen fungerande medan databasen tar igen sig.
Att lägga till en nullable kolumn är vanligtvis säkert. Att lägga till en kolumn med en icke-null default kan vara riskabelt på äldre Postgres-versioner eftersom det kan skriva om hela tabellen.
En säkrare strategi är en tvåstegsändring: lägg till kolumnen som NULL utan default, backfilla i batcher, sätt default för nya rader och lägg till NOT NULL först när datan är ren.
Om du måste tvinga igenom ett default omedelbart, kräv en förklaring av låsbeteendet för din Postgres-version och en fallback-plan om körtiden blir längre än väntat.
För index på stora tabeller, begär CREATE INDEX CONCURRENTLY så reads och writes kan fortsätta. Kräv också en notering att det inte kan köras inuti en transaktion, vilket betyder att ditt migrationsverktyg måste hantera ett icke-transaktionellt steg.
För foreign keys är det oftast säkrare att lägga till constraint som NOT VALID först, och sedan validera senare. Det gör initialändringen snabbare samtidigt som FK upprätthålls för nya skrivningar.
När du skärper constraints (NOT NULL, UNIQUE, CHECK), be om "rensa först, verkställ sedan." Migrationen bör upptäcka ogiltiga rader, fixa dem och först därefter aktivera den strängare regeln.
Om du vill ha en kort checklista att klistra in i prompts, håll den kärnfull:
Backfills är där det flesta migrationsproblem visar sig, inte ALTER TABLE. De säkraste prompts behandlar backfills som kontrollerade jobb: mätbara, återstartbara och snälla mot produktion.
Börja med acceptanskontroller som är lätta att köra och svåra att ifrågasätta: förväntade radsummor, ett mål för null-rate, och några spotchecks (t.ex. jämför gammal vs ny värde för 20 slumpmässiga ID:n).
Be sedan om en batchplan. Batcher håller lås korta och minskar överraskningar. En bra begäran specificerar:
Kräv idempotens eftersom backfills kan misslyckas halvvägs. SQL bör vara säker att köra om utan duplication eller korruption. Typiska mönster är "update endast där den nya kolumnen är NULL" eller en deterministisk regel där samma input alltid ger samma output.
Stav också ut hur appen förblir korrekt medan backfill körs. Om nya skrivningar fortsätter behöver du en brygga: dual-write i appkod, en temporär trigger eller read-fallback-logik (läs ny när den finns, annars gammal). Ange vilken strategi du säkert kan deploya.
Bygg slutligen in paus och återuppta i designen. Be om progress-tracking och checkpoints, som en liten tabell som lagrar senast bearbetat ID och en fråga som rapporterar framsteg (rader uppdaterade, sista ID, starttid).
Exempel: du lägger till users.full_name härledd från first_name och last_name. En säker backfill uppdaterar bara rader där full_name IS NULL, kör i ID-intervall, loggar senast uppdaterat ID och ser till att nya signups förblir korrekta via dual-write tills switchen är klar.
En rollback-plan är inte bara "skriv en down-migration." Det är två problem: ångra schemaändringen och hantera data som ändrats medan den nya versionen var live. Schemarollback är ofta möjlig. Datarollback är ofta inte möjlig, om du inte planerat för det.
Var tydlig med vad rollback betyder för din ändring. Om du tar bort en kolumn eller skriver över värden in-place, kräva ett realistiskt svar som: "Rollback återställer appkompatibilitet, men ursprunglig data kan inte återställas utan en snapshot." Den ärligheten håller dig säker.
Be om klara rollback-triggers så ingen börjar argumentera under en incident. Exempel:
Kräv hela rollback-paketet, inte bara SQL: down-migration SQL (endast om säker), appsteg för att återgå till kompatibilitet och hur man stoppar bakgrundsjobb.
Denna promptstruktur räcker oftast:
Produce a rollback plan for this migration.
Include: down migration SQL, app config/code switches needed for compatibility, and the exact order of steps.
State what can be rolled back (schema) vs what cannot (data) and what evidence we need before deciding.
Include rollback triggers with thresholds.
Innan du släpper, fånga en lättviktig "safetysnapshot" så att du kan jämföra före/efter:
Var också tydlig med när du inte ska rulla tillbaka. Om du bara lagt till en nullable kolumn och appen dual-skriver, är en framåtfokuserad fix (hotfixkod, pausa backfill, återuppta) ofta säkrare än att revert:a och skapa mer driftsskillnad.
AI kan skriva SQL snabbt, men den kan inte se din produktionsdatabas. De flesta fel händer när prompten är vag och modellen fyller i luckorna.
En vanlig fälla är att hoppa över nuvarande schema. Om du inte klistrar in tabellens definition, index och constraints, kan den genererade SQL:en rikta mot kolumner som inte finns eller missa en unik regel som gör en backfill lång och lås-intensiv.
Ett annat misstag är att skicka expand, backfill och contract i en deploy. Det tar bort din nödbroms. Om backfill kör länge eller felar halvvägs sitter du med en app som förväntar sig slutligt tillstånd.
De problem som oftast uppträder:
Ett konkret exempel: "byta namn på en kolumn och uppdatera appen." Om den genererade planen byter namn och backfyllar i en enda transaktion, kan en lång backfill hålla lås och bryta live-trafik. En säkrare prompt tvingar fram små batcher, tydliga timeouts och verifieringsfrågor innan den tar bort den gamla vägen.
Staging är där du hittar problem som aldrig dyker upp i en pytteliten dev-databas: långa lås, överraskande nulls, saknade index och glömda kodvägar.
Först, kontrollera att schemat matchar planen efter migration: kolumner, typer, defaults, constraints och index. En snabb blick räcker inte. Ett saknat index kan göra en säker backfill till en långsam mardröm.
Kör sedan migrationen mot en realistisk dataset. Helst är det en färsk kopia av produktion med känsliga fält maskade. Om du inte kan göra det, matcha åtminstone produktionsvolym och hotspots (stora tabeller, breda rader, tungt indexerade tabeller). Spela in tider för varje steg så du vet vad du kan förvänta dig i production.
En kort staging-checklista:
Testa slutligen riktiga användarflöden, inte bara SQL. Skapa, uppdatera och läs poster som påverkas av ändringen. Om expand/contract är planen, bekräfta att båda scheman fungerar tills slutlig cleanup.
Föreställ dig att du har en users.name-kolumn som lagrar fullständiga namn som "Ada Lovelace." Du vill ha first_name och last_name, men du kan inte bryta signups, profiler eller admin-skärmar medan ändringen rullas ut.
Börja med ett expandsteg som är säkert även om ingen kodändring deployas än: lägg till nullable kolumner, behåll den gamla kolumnen och undvik långa lås.
ALTER TABLE users ADD COLUMN first_name text;
ALTER TABLE users ADD COLUMN last_name text;
Uppdatera sedan appbeteendet för att stödja båda schemana. I Release 1 ska appen läsa från de nya kolumnerna när de finns, falla tillbaka till name när de är null, och skriva till båda så att ny data förblir konsekvent.
Nästa steg är backfill. Kör ett batchjobb som uppdaterar en liten mängd rader per gång, loggar framsteg och kan pausas säkert. Till exempel: uppdatera users där first_name är null i stigande ID-ordning, 1 000 åt gången, och logga hur många rader som ändrades.
Innan du skärper regler, validera i staging:
first_name och last_name och sätter fortfarande namename finnsusers blir inte märkbart långsammareRelease 2 byter läsningar till de nya kolumnerna endast. Först efter det bör du lägga till constraints (som SET NOT NULL) och ta bort name, helst i en senare, separat deploy.
För rollback, håll det tråkigt. Appen läser name under övergången och backfillen är stoppbar. Om du behöver rulla tillbaka Release 2, växla läsningar tillbaka till name och lämna de nya kolumnerna på plats tills allt är stabilt igen.
Behandla varje ändring som en liten runbook. Målet är inte en perfekt prompt. Det är en rutin som tvingar fram rätt detaljer: schema, constraints, körplan och rollback.
Standardisera vad varje migrationsbegäran måste inkludera:
Bestäm vem som äger varje steg innan någon kör SQL. En enkel uppdelning förhindrar "alla trodde att någon annan gjorde det": utvecklare äger prompt och migrationskod, ops äger produktionstidpunkt och övervakning, QA verifierar staging-beteende och kantfall, och en person är slutligt go/no-go.
Om du bygger appar via chat kan det hjälpa att skissera sekvensen innan du genererar någon SQL. För team som använder Koder.ai är Planning Mode ett naturligt ställe att skriva ned den sekvensen, och snapshots plus rollback kan minska blast radius om något oväntat händer under utrullning.
Efter att du skickat, schemalägg contract-cleanup omedelbart medan kontexten är färsk, så att gamla kolumner och temporär kompatibilitetskod inte ligger kvar i månader.
En schemaändring är riskfylld när appkod, databastillstånd och utsläppstidpunkt slutar matcha.
Vanliga felsätt:
Använd ett expand/contract-mönster:
Det håller både gamla och nya appversioner fungerande under utrullningen.
Modellen kan generera giltig SQL som ändå är osäker för din arbetsbelastning.
Typiska AI-specifika risker:
Behandla AI-output som ett utkast och kräva en körplan, tester och rollback-steg.
Inkludera endast fakta som migrationen beror på:
CREATE TABLE-utdrag (plus index, FKs, UNIQUE/CHECK-constraints, triggers)Standardregel: separera dem.
En praktisk uppdelning:
Att slå ihop allt gör fel svårare att felsöka och rulla tillbaka.
Föredra detta mönster:
ADD COLUMN ... NULL utan default (snabbt)NOT NULL först efter verifieringAtt direkt lägga till en icke-null default kan vara riskabelt på vissa Postgres-versioner eftersom det kan orsaka en fullständig omskrivning av tabellen. Om ett omedelbart defaultvärde krävs, be om en förklaring av låsbeteendet för din version och en fallback-plan.
Be om:
CREATE INDEX CONCURRENTLY för stora/varma tabellerFör verifiering, inkludera en snabb kontroll att indexet finns och används (t.ex. jämför EXPLAIN-plan före/efter i staging).
Använd NOT VALID först, validera senare:
NOT VALID så att initialt steg blir mindre störandeVALIDATE CONSTRAINT i ett separat steg när du kan övervaka detDet upprätthåller FK för nya skrivningar samtidigt som du kontrollerar när den dyra valideringen sker.
En bra backfill är batchad, idempotent och återstartbar.
Praktiska krav:
WHERE new_col IS NULL)Målsättning för rollback: återställ appkompatibilitet snabbt, även om data inte perfekt går att återskapa.
En genomförbar rollback-plan bör innehålla:
Oftast är det säkraste att växla läsningar tillbaka till det gamla fältet medan nya kolumner lämnas på plats.
Det förhindrar gissningar och tvingar rätt ordning.
Det gör backfills uthärdliga under verklig trafik.