The dangerous migration is the one that looks trivial — one ALTER TABLE that locks a hot table for nine minutes. Have AI draft the batched online migration, the rollback script and the row-count checks as one package, then run it through a review-gate checklist and a staging replay before production sees it.
Tier: Core · Workflow area: Schema & Change Management · ~40 minutes
AI knows the online-migration patterns cold: batched backfills, NOT VALID constraints, concurrent index builds, lock-timeout wrappers. What it does not know is your table sizes, your replica lag tolerance, or your maintenance windows — which is why the output is a draft package that must survive a gate chain: human checklist, staging replay at production scale, then prod. No gate, no ship.
Specify the change with its danger context. The prompt gets the operational facts that make migrations fail:
Draft a production migration package for PostgreSQL 16.
Change: add column risk_score numeric NOT NULL DEFAULT 0 to orders,
backfilled from risk_events, then add a CHECK (risk_score >= 0).
Operational facts:
- orders: 480M rows, ~900 writes/sec sustained, replicas with 5s
lag SLA, pgbouncer in transaction mode.
- Maintenance window: none. Must run online.
- Migration tooling: Flyway, versioned scripts.
Produce FOUR artifacts:
1. FORWARD script — batched, resumable, with lock_timeout and
statement_timeout set, batch size as a variable, progress logging.
2. ROLLBACK script — truly inverse, also online-safe.
3. VERIFICATION queries — pre/post row counts, backfill completeness,
constraint validity, replica-lag checkpoints between phases.
4. HAZARD LIST — every lock this takes, on what, for how long, and
what happens to concurrent writes in each phase.
Use NOT VALID + VALIDATE for the constraint. Never rewrite the
whole table in one statement.
Read the draft for the classic seeded hazards. Generated migrations fail in recurring ways; check for each explicitly: a single-statement backfill hiding inside the batching (UPDATE orders SET ... with no batch predicate), constraint validation without NOT VALID first, missing lock_timeout (a blocked ALTER queues behind one long transaction and then blocks everyone else), and index creation without CONCURRENTLY. The healthy core of the forward script looks like:
SET lock_timeout = '2s';
SET statement_timeout = '0';
ALTER TABLE orders ADD COLUMN risk_score numeric; -- no default: metadata-only
-- Batched backfill, resumable by PK range
DO $$
DECLARE last_id bigint := 0; batch int := 20000; n bigint;
BEGIN
LOOP
UPDATE orders o SET risk_score = COALESCE(r.score, 0)
FROM risk_events r
WHERE o.id > last_id AND o.id <= last_id + batch
AND r.order_id = o.id AND o.risk_score IS NULL;
GET DIAGNOSTICS n = ROW_COUNT;
last_id := last_id + batch;
RAISE NOTICE 'through id %, updated %', last_id, n;
EXIT WHEN last_id >= (SELECT max(id) FROM orders);
PERFORM pg_sleep(0.1); -- let replicas breathe
END LOOP;
END $$;
ALTER TABLE orders ALTER COLUMN risk_score SET DEFAULT 0;
UPDATE orders SET risk_score = 0 WHERE risk_score IS NULL; -- stragglers, batched too if large
ALTER TABLE orders ALTER COLUMN risk_score SET NOT NULL; -- PG12+: validates via scan; consider CHECK NOT VALID route on huge tables
ALTER TABLE orders ADD CONSTRAINT orders_risk_nonneg
CHECK (risk_score >= 0) NOT VALID;
ALTER TABLE orders VALIDATE CONSTRAINT orders_risk_nonneg; -- takes only SHARE UPDATE EXCLUSIVE
Run the review-gate checklist — signed, per item. The checklist is the human gate; every line gets initials:
[ ] Every statement's lock level and duration identified (hazard list
cross-checked against the docs, not just the AI's claims)
[ ] Backfill is batched AND resumable from a crash mid-run
[ ] Rollback script replayed mentally against EACH phase — inverse
works from any partial state, not just full completion
[ ] lock_timeout set; retry behavior on timeout defined
[ ] Replica lag checkpoints between phases; abort threshold named
[ ] Verification queries have expected values written down BEFORE
the run (orders count = backfilled + defaulted)
[ ] Flyway versioning: forward is V-numbered, rollback filed as the
documented U/undo path; both in the PR
Replay in staging at production scale — and rehearse the abort. Run the full package against a restored production-sized copy with synthetic write load running. Measure: total duration, max replica lag, lock waits experienced by the synthetic writers. Then rehearse the failure: kill the backfill mid-run, confirm resumability; run the rollback from the half-done state, confirm the verification queries catch the partial state.
Execute in production phase-by-phase with checkpoints. Run each phase, run its verification query, check replica lag, proceed or abort. A migration run log — phase, start/end, verification result, lag observed — goes into the ticket as it happens, not from memory afterward.
Close with the post-verification. Final row counts reconcile, constraint is validated, no orphaned NULLs, app error rates flat. The package plus the run log is the change record.
--max-lag-millis, --chunk-size, --postpone-cut-over-flag-file) and the cut-over/rollback plan instead of raw ALTERs, and replay the whole ghost migration in staging including the cut-over.TOP (@batch) loops and WAITFOR DELAY; watch lock escalation at ~5k locks and ask the AI to design batches below the escalation threshold, verified with Extended Events in staging.DBMS_REDEFINITION for table-rewriting changes and ask AI to draft the redefinition script plus the interim-table verification; DEFAULT additions are metadata-optimized on modern versions, but confirm edition-specific behavior before trusting it.Never run AI-generated DDL against production directly from the chat window. The package ships through your migration tool, with version control, the signed checklist and the staging replay behind it — the same gate chain every schema change earns, no matter who or what drafted it.
AI compresses the drafting of a safe online migration from hours to minutes; the gate chain — signed checklist, production-scale replay, rehearsed rollback — is what makes it actually safe. Speed on the draft, never on the gates.