Leer prompts voor Claude Code PostgreSQL-migraties voor veilige expand-contract wijzigingen, backfills en rollback-plannen, plus wat je in staging moet verifiëren vóór release.

Een PostgreSQL-schemawijziging lijkt eenvoudig totdat die echte traffic en echte data tegenkomt. Het risicovolle deel is meestal niet de SQL zelf. Het is wanneer appcode, databasestatus en deploy-timing niet meer overeenkomen.
De meeste fouten zijn praktisch en pijnlijk: een deploy breekt omdat oude code een nieuwe kolom aanraakt, een migratie lockt een veelgebruikte tabel en timeouts nemen toe, of een "snel" wijziging verwijdert of herschrijft stilletjes data. Zelfs wanneer er niets crasht, kun je subtiele bugs uitrollen zoals verkeerde defaults, gebroken constraints of indexen die nooit klaar zijn met bouwen.
AI-gegenereerde migraties voegen nog een risicolaag toe. Tools kunnen geldige SQL produceren die toch onveilig is voor jouw workload, je datavolume of je releaseproces. Ze kunnen ook tabelnamen raden, langlopende locks missen of rollback wegwuiven omdat down-migraties lastig zijn. Als je Claude Code gebruikt voor migraties, heb je guardrails en concrete context nodig.
Als dit artikel zegt dat een wijziging "veilig" is, bedoelt het drie dingen:
Het doel is dat migraties routinewerk worden: voorspelbaar, testbaar en saai.
Begin met een paar niet-onderhandelbare regels. Ze houden het model gefocust en voorkomen dat je een wijziging uitrolt die alleen op je laptop werkt.
Splits werk in kleine stappen. Een schemawijziging, een data-backfill, een appwijziging en een opruimstap zijn verschillende risico's. Ze bundelen maakt het moeilijker te zien wat er kapotging en moeilijker terug te draaien.
Geef de voorkeur aan additieve wijzigingen boven destructieve. Een kolom, index of tabel toevoegen is meestal laag risico. Het hernoemen of verwijderen van objecten is waar outages gebeuren. Doe het veilige deel eerst, verplaats de app, en verwijder het oude pas als je zeker weet dat het ongebruikt is.
Laat de app beide vormen voor een tijdje verdragen. Code moet zowel de oude kolom als de nieuwe kunnen lezen tijdens rollout. Dit voorkomt de veelvoorkomende race waarbij sommige servers nieuwe code draaien terwijl de database nog oud is (of andersom).
Behandel migraties als productiecode, niet als een snel script. Zelfs als je bouwt met een platform zoals Koder.ai (Go-backend met PostgreSQL, plus React of Flutter clients), wordt de database door alles gedeeld. Fouten zijn kostbaar.
Als je een compacte set regels wilt om bovenaan elk SQL-verzoek te zetten, gebruik iets als:
Een praktisch voorbeeld: in plaats van een kolom te hernoemen waar je app van afhankelijk is, voeg een nieuwe kolom toe, backfill langzaam, deploy code die eerst nieuw leest dan oud, en verwijder het oude pas later.
Claude kan fatsoenlijke SQL schrijven op basis van een vage aanvraag, maar veilige migraties hebben context nodig. Behandel je prompt als een mini design brief: laat zien wat er is, leg uit wat er niet kapot mag gaan en definieer wat "veilig" betekent voor je rollout.
Begin met alleen de databasefeiten die ertoe doen. Voeg de tabeldefinitie toe plus relevante indexen en constraints (primary keys, unique constraints, foreign keys, check constraints, triggers). Als gerelateerde tabellen betrokken zijn, plak die snippets ook. Een kleine, accurate excerpt voorkomt dat het model namen raadt of een belangrijke constraint mist.
Voeg real-world schaal toe. Row counts, tabelgrootte, schrijftempo en piektraffic moeten het plan beïnvloeden. "200M rows en 1k writes/sec" is een andere migratie dan "20k rows en vooral reads." Voeg ook je Postgres-versie toe en hoe migraties in jouw systeem draaien (één transactie vs meerdere stappen).
Beschrijf hoe de applicatie de data gebruikt: de belangrijke reads, writes en background jobs. Voorbeelden: "API leest op email", "workers updaten status" of "reports scannen op created_at." Dit bepaalt of je expand/contract nodig hebt, feature flags, en hoe veilig een backfill zal zijn.
Wees tenslotte expliciet over constraints en deliverables. Een eenvoudige structuur werkt goed:
Vragen om zowel SQL als een runplan dwingt het model na te denken over ordering, risico en wat je moet checken voordat je uitrolt.
Het expand/contract migratiepatroon verandert een PostgreSQL-database zonder de app te breken terwijl de wijziging bezig is. In plaats van een enkele risicovolle switch maak je de database gedurende enige tijd compatibel met zowel de oude als de nieuwe vorm.
Zie het als: voeg veilig nieuwe dingen toe (expand), verplaats traffic en data geleidelijk, en verwijder daarna pas de oude onderdelen (contract). Dit is vooral nuttig bij AI-geassisteerd werk omdat het je dwingt te plannen voor het rommelige midden.
Een praktisch flow ziet er zo uit:
Gebruik dit patroon wanneer gebruikers mogelijk nog op oude appcode zitten terwijl de database verandert. Dat geldt voor multi-instance deployments, mobiele apps die langzaam updaten, of elke release waarbij een migratie minuten of uren kan duren.
Een handige tactiek is plannen voor twee releases. Release 1 doet expand plus compatibility zodat niets breekt als de backfill incompleet is. Release 2 doet de contract pas nadat je hebt bevestigd dat de nieuwe code en nieuwe data op hun plek zijn.
Kopieer deze template en vul de haken in. Het dwingt Claude Code om SQL te produceren die je kunt draaien, checks om te bewijzen dat het werkte en een rollback-plan dat je daadwerkelijk kunt volgen.
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
Twee extra regels die in de praktijk helpen:
RISK: blocks writes, plus wanneer die te draaien (off-peak vs anytime).Kleine schemawijzigingen kunnen nog steeds pijn doen als ze lange locks nemen, grote tabellen herschrijven of halverwege falen. Wanneer je Claude Code gebruikt voor migraties, vraag dan om SQL die rewrites vermijdt en je app laat blijven werken terwijl de database bijwerkt.
Een nullable kolom toevoegen is meestal veilig. Een kolom toevoegen met een non-null default kan riskant zijn op oudere Postgres-versies omdat het de hele tabel kan herschrijven.
Een veiliger benadering is een twee-stappen wijziging: voeg de kolom toe als NULL zonder default, backfill in batches, stel dan de default in voor nieuwe rijen en voeg NOT NULL toe zodra de data schoon is.
Als je onmiddellijk een default moet afdwingen, eis dan een uitleg van lock-gedrag voor jouw Postgres-versie en een fallback-plan als de runtime langer is dan verwacht.
Voor indexen op grote tabellen, vraag om CREATE INDEX CONCURRENTLY zodat reads en writes blijven stromen. Vereis ook een notitie dat het niet binnen een transactieblok kan draaien, wat betekent dat je migratietool een niet-transactiestap moet ondersteunen.
Voor foreign keys is de veiligere route meestal eerst de constraint toevoegen als NOT VALID, en later valideren. Dit maakt de initiële wijziging sneller terwijl het de FK voor nieuwe writes afdwingt.
Wanneer je constraints strenger maakt (NOT NULL, UNIQUE, CHECK), vraag om "first clean, then enforce." De migratie moet slechte rijen detecteren, ze repareren en pas dan de strengere regel inschakelen.
Als je een korte checklist wilt plakken in prompts, houd het compact:
Backfills zijn waar de meeste migratiepijn zichtbaar wordt, niet de ALTER TABLE. De veiligste prompts behandelen backfills als gecontroleerde jobs: meetbaar, herstartbaar en vriendelijk voor productie.
Begin met acceptatiechecks die makkelijk uit te voeren en moeilijk te betwisten zijn: verwachte row counts, een target null-rate en een paar spot checks (bijv. vergelijk oude vs nieuwe waarden voor 20 willekeurige IDs).
Vraag daarna om een batching-plan. Batches houden locks kort en verminderen verrassingen. Een goede vraag specificeert:
Eis idempotentie omdat backfills halverwege kunnen falen. De SQL moet veilig opnieuw te draaien zijn zonder duplicatie of corruptie. Typische patronen zijn "update alleen waar de nieuwe kolom NULL is" of een deterministische regel waarbij dezelfde input altijd dezelfde output geeft.
Omschrijf ook hoe de app correct blijft terwijl de backfill draait. Als er nieuwe writes blijven binnenkomen, heb je een brug nodig: dual-write in appcode, een tijdelijke trigger, of read-fallback logica (lees nieuw wanneer aanwezig, anders oud). Geef aan welke aanpak je veilig kunt deployen.
Bouw tenslotte pauze en hervat in het ontwerp in. Vraag om progress-tracking en checkpoints, zoals een kleine tabel die de laatst verwerkte ID opslaat en een query die vooruitgang rapporteert (rows updated, last ID, starttijd).
Voorbeeld: je voegt users.full_name toe afgeleid van first_name en last_name. Een veilige backfill update alleen rijen waar full_name IS NULL, draait in ID-bereiken, registreert de laatst bijgewerkte ID en houdt nieuwe aanmeldingen consistent via dual-write totdat de switch-over compleet is.
Een rollback-plan is niet alleen "schrijf een down-migratie." Het zijn twee problemen: het ongedaan maken van de schemawijziging en het omgaan met data die veranderd is terwijl de nieuwe versie live was. Schema-rollback is vaak mogelijk. Data-rollback is vaak niet mogelijk, tenzij je er vooraf voor hebt gepland.
Wees expliciet over wat rollback betekent voor jouw wijziging. Als je een kolom dropt of waarden in-place herschrijft, eis dan een realistisch antwoord zoals: "Rollback herstelt app-compatibiliteit, maar originele data kan niet worden teruggewonnen zonder een snapshot." Die eerlijkheid houdt je veilig.
Vraag om duidelijke rollback-triggers zodat tijdens een incident niemand gaat discussiëren. Voorbeelden:
Eis het volledige rollback-pakket, niet alleen SQL: down-migration SQL (alleen als veilig), app-stappen om compatibel te blijven, en hoe background jobs te stoppen.
Dit promptpatroon is meestal genoeg:
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.
Voordat je uitrolt, maak een lichte "safety snapshot" zodat je voor en na kunt vergelijken:
Wees ook duidelijk wanneer je niet moet terugdraaien. Als je alleen een nullable kolom hebt toegevoegd en de app dual-write doet, is een forward-fix (hotfix code, pauzeer de backfill, hervat later) vaak veiliger dan revert en nog meer drift creëren.
AI kan snel SQL schrijven, maar het kan je productie-database niet zien. De meeste fouten ontstaan wanneer de prompt vaag is en het model de lege plekken invult.
Een veelgemaakte val is het overslaan van het huidige schema. Als je niet de tabeldefinitie, indexen en constraints plakt, kan de SQL kolommen targetten die niet bestaan of een unieke regel missen die een backfill tot een lange, lock-rijke operatie maakt.
Een andere fout is expand, backfill en contract in één deploy te zetten. Dat verwijdert je escape hatch. Als de backfill lang duurt of halverwege fout gaat, zit je vast met een app die de uiteindelijke staat verwacht.
De problemen die het vaakst voorkomen:
Een concreet voorbeeld: "rename a column and update the app." Als het gegenereerde plan rename en backfill in één transactie doet, kan een trage backfill locks vasthouden en live traffic breken. Een veiligere prompt dwingt kleine batches, expliciete timeouts en verificatiequeries voordat je het oude pad verwijdert.
Staging is waar je problemen vindt die nooit op een kleine dev-database optreden: lange locks, verrassende nulls, ontbrekende indexen en vergeten codepaden.
Controleer eerst dat het schema overeenkomt met het plan na de migratie: kolommen, types, defaults, constraints en indexen. Een snelle blik is niet genoeg. Eén ontbrekende index kan een veilige backfill veranderen in een trage ramp.
Draai daarna de migratie tegen een realistische dataset. Idealiter is dat een recente kopie van productie met gemaskeerde gevoelige velden. Als dat niet kan, match dan in ieder geval productievolume en hotspots (grote tabellen, brede rijen, veel geïndexeerde tabellen). Leg timings vast voor elke stap zodat je weet wat te verwachten in productie.
Een korte staging-checklist:
Test tenslotte echte gebruikersflows, niet alleen SQL. Maak, update en lees records die door de wijziging geraakt worden. Als expand/contract het plan is, bevestig dat beide schema's werken totdat de definitieve cleanup gedaan is.
Stel dat je een users.name kolom hebt die volledige namen opslaat zoals "Ada Lovelace." Je wilt first_name en last_name, maar je mag geen signups, profielen of adminschermen breken terwijl de wijziging uitrolt.
Begin met een expand-stap die veilig is, zelfs als er geen codewijzigingen tegelijk worden uitgerold: voeg nullable kolommen toe, houd de oude kolom en vermijd lange locks.
ALTER TABLE users ADD COLUMN first_name text;
ALTER TABLE users ADD COLUMN last_name text;
Update daarna het app-gedrag om beide schema's te ondersteunen. In Release 1 moet de app van de nieuwe kolommen lezen wanneer die aanwezig zijn, terugvallen op name wanneer ze null zijn, en naar beide schrijven zodat nieuwe data consistent blijft.
Dan komt de backfill. Draai een batchjob die een klein stuk rijen per run bijwerkt, voortgang logt en veilig gepauzeerd kan worden. Bijvoorbeeld: update users waar first_name null is in oplopende ID-volgorde, 1.000 tegelijk, en log hoeveel rijen veranderden.
Voordat je regels aanscherpt, valideer in staging:
first_name en last_name en zetten nog steeds namename aanwezig isusers zijn niet merkbaar tragerRelease 2 schakelt reads om naar alleen de nieuwe kolommen. Pas daarna voeg je constraints toe (zoals SET NOT NULL) en drop je name, bij voorkeur in een latere, aparte deploy.
Voor rollback: houd het saai. De app blijft name lezen tijdens de transitie en de backfill is stopbaar. Als je Release 2 moet terugdraaien, schakel je reads terug naar name en laat je de nieuwe kolommen staan totdat je weer stabiel bent.
Behandel elke wijziging als een klein runbook. Het doel is niet een perfecte prompt, maar een routine die de juiste details afdwingt: schema, constraints, runplan en rollback.
Standardiseer wat elk migratieverzoek moet bevatten:
Bepaal wie elke stap bezit voordat iemand SQL draait. Een eenvoudige taakverdeling voorkomt "iedereen dacht dat iemand anders het deed": ontwikkelaars bezitten de prompt en migratiecode, ops bepaalt productie-timing en monitoring, QA verifieert staging-gedrag en edgecases, en één persoon is de uiteindelijke go/no-go.
Als je apps bouwt via chat, helpt het om de volgorde op te schrijven voordat je SQL genereert. Voor teams die Koder.ai gebruiken is Planning Mode een natuurlijke plek om die volgorde vast te leggen, en snapshots plus rollback kunnen de blast radius verkleinen als er iets onverwachts gebeurt tijdens rollout.
Plan om na uitrol direct de contract-cleanup te schedulen terwijl de context nog vers is, zodat oude kolommen en tijdelijke compatibiliteitscode niet maanden blijven liggen.
Een schemawijziging is riskant wanneer appcode, databasestatus en deploy-timing uit sync raken.
Veelvoorkomende fouten:
Gebruik de expand/contract aanpak:
Dit houdt oude en nieuwe appversies werkend tijdens de rollout.
Omdat het model SQL kan genereren die geldig is maar onveilig voor jouw workload.
Typische AI-specifieke risico's:
Behandel AI-uitvoer als een concept: eis een runplan, checks en rollback-stappen.
Neem alleen de feiten op waarop de migratie afhankelijk is:
CREATE TABLE-snippets (plus indexes, FKs, UNIQUE/CHECK constraints, triggers)Standaardregel: scheid ze.
Een praktische verdeling:
Alles bundelen maakt fouten lastiger te diagnosticeren en terug te draaien.
Voorkeurspatroon:
ADD COLUMN ... NULL zonder default (snel)NOT NULL toe pas na verificatieEen niet-null default toevoegen kan riskant zijn op sommige Postgres-versies omdat het de hele tabel kan herschrijven. Als je een onmiddellijke default nodig hebt, vraag dan om lock/runtime aantekeningen en een veiliger fallback.
Vraag om:
CREATE INDEX CONCURRENTLY voor grote/drukke tabellenVoor verificatie: voeg een snelle check toe dat de index bestaat en gebruikt wordt (bijv. vergelijk een EXPLAIN-plan voor/na in staging).
Gebruik eerst NOT VALID, valideer later:
NOT VALID zodat de initiële stap minder disruptief isVALIDATE CONSTRAINT in een aparte stap uit wanneer je het kunt monitorenDit dwingt nog steeds de FK af voor nieuwe writes, terwijl je zelf bepaalt wanneer de dure validatie plaatsvindt.
Een goede backfill is gebatched, idempotent en herstartbaar.
Praktische eisen:
WHERE new_col IS NULL)Doel rollback: herstel snel app-compatibiliteit, ook al is data niet perfect teruggedraaid.
Een bruikbaar rollback-plan moet bevatten:
Vaak is de veiligste rollback het weer laten lezen van het oude veld terwijl de nieuwe kolommen blijven bestaan.
Dit voorkomt gokken en dwingt de juiste volgorde af.
Dit maakt backfills overleefbaar onder echt verkeer.