sql-migration-review
Skill0 starsChanged 25 days agoMITView on GitHub
Use when reviewing a database migration before it reaches production, especially on PostgreSQL or MySQL tables with real traffic. Covers lock behavior, backward-compatible expand and contract, concurrent index builds, batched backfills, column type and rename safety, rollback, and observability. Does not cover schema design from scratch or query tuning.
In this repository18 other files it gives its agents.
- docs/agents.mdAGENTS.md
- agent-skill-authorSkill
- api-contract-reviewSkill
- codex-cli-workflowsSkill
- codex-plugin-authorSkill
- context-budgetSkill
- dependency-auditSkill
- docs-writerSkill
- git-recoverySkill
- incident-postmortemSkill
- mcp-server-builderSkill
- performance-profileSkill
- pr-reviewerSkill
- release-engineerSkill
- repo-onboardingSkill
- skill-security-reviewSkill
- terminal-debuggerSkill
- test-first-fixSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
18 rules. About 0 are common, 16 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈16
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 20
Use this skill when:
- Distinctive: line 35
Write rate and peak traffic window.
- Distinctive: line 75
Never drop or rename in the same deploy that stops using the old shape.
- Distinctive: line 108
If it fails, it leaves an invalid index.
- Very specific: line 148
Do not run one UPDATE over millions of rows in a single transaction.
- Distinctive: line 161
If data was rewritten, is there a backup or a reversible mapping?
- Distinctive: line 169
Run the migration before the code that needs it, when the change is additive.
- Distinctive: line 170
Run destructive steps after the old code is fully gone.
- Distinctive: line 172
Keep a dashboard open for the migration window.
- Distinctive: line 173
Stop on the first sign of saturation.
- Distinctive: line 203
Do not run a migration on production without a measured table size and traffic estimate.
- Distinctive: line 204
Do not combine additive and destructive changes in one deploy.
- Very specific: line 205
Do not use CREATE INDEX CONCURRENTLY inside a transaction wrapper.
- Distinctive: line 206
Do not backfill in a single unbounded update.
- Distinctive: line 207
Do not assume a lock is brief because the statement is short.
- Distinctive: line 208
Do not drop a column before the old code stops reading it.
- Distinctive: line 209
Do not rely on a down migration that loses data.
- Distinctive: line 210
Do not test on an empty table and infer production behavior.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
SQL Migration Review
A migration is a code change that runs against live data and cannot be rolled back by redeploying the application. The review question is not "does the SQL work" but "what does it lock, for how long, and can old and new code both run while it happens".
When to use
Use this skill when:
- A migration adds, drops, renames, or retypes a column.
- An index is created on a large table.
- Data is backfilled or rewritten.
- A constraint is added or validated.
- A table is split, merged, or moved.
- A deploy needs zero downtime or a rollback path.
Workflow
1. Establish the facts
- Engine and version.
- Table size: rows, bytes, index bytes.
- Write rate and peak traffic window.
- Replication topology and lag tolerance.
- Migration tool and whether it wraps statements in a transaction.
- Whether the app deploy and the migration run together or separately.
- Rollback requirement.
SELECT relname, n_live_tup, pg_size_pretty(pg_total_relation_size(relid)) AS total
FROM pg_stat_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 20;Without size and traffic, "safe" is a guess.
2. Classify each statement by lock
PostgreSQL examples:
| Statement | Lock | Risk |
ADD COLUMN ... NULL (no default, or non-volatile default on modern PG) | brief ACCESS EXCLUSIVE | Low |
ADD COLUMN ... DEFAULT volatile | ACCESS EXCLUSIVE, rewrites table (older versions) | High |
DROP COLUMN | ACCESS EXCLUSIVE | Brief but blocks reads |
ALTER COLUMN TYPE | ACCESS EXCLUSIVE, often rewrite | High |
SET NOT NULL | ACCESS EXCLUSIVE with full scan | High |
ADD CONSTRAINT ... NOT VALID | SHARE ROW EXCLUSIVE | Low, validation later |
VALIDATE CONSTRAINT | SHARE UPDATE EXCLUSIVE | Low, scans table |
CREATE INDEX | SHARE, blocks writes | High on large tables |
CREATE INDEX CONCURRENTLY | SHARE UPDATE EXCLUSIVE | Low, cannot run in a transaction |
VACUUM | SHARE UPDATE EXCLUSIVE | Low |
MySQL differences:
ALGORITHM=INPLACEandLOCK=NONEchange behavior; verify support per operation and version.ADD COLUMNsupport and rebuild behavior changed across 5.7 and 8.0.- Online DDL still needs headroom for concurrent DML.
3. Use expand and contract for breaking changes
Never drop or rename in the same deploy that stops using the old shape.
Expand (deploy 1):
ALTER TABLE orders ADD COLUMN customer_ref text;Backfill in batches. Add a trigger or dual-write if the application cannot write both yet.
Migrate (deploy 2):
- Application reads the new column and writes both.
- Verify counts match.
Contract (deploy 3, after old code is gone):
ALTER TABLE orders DROP COLUMN customer_id;A rename is a drop plus an add. Treat it as three deploys.
4. Create indexes concurrently
CREATE INDEX CONCURRENTLY idx_orders_customer_ref
ON orders (customer_ref);Rules:
- Cannot run inside a transaction. Configure the migration tool accordingly.
- If it fails, it leaves an invalid index. Drop it before retrying.
- It takes longer than a blocking build.
- It still consumes I/O; schedule it off peak.
SELECT indexrelid::regclass, indisvalid
FROM pg_index
WHERE NOT indisvalid;5. Add constraints safely
ALTER TABLE orders
ADD CONSTRAINT orders_total_positive CHECK (total_cents >= 0) NOT VALID;
ALTER TABLE orders VALIDATE CONSTRAINT orders_total_positive;NOT VALID avoids a full-table scan under an exclusive lock. VALIDATE takes a weaker lock and can run later.
For foreign keys, the same pattern applies. Validate after the backfill.
6. Backfill in batches
UPDATE orders
SET customer_ref = legacy_customer_id
WHERE id IN (
SELECT id FROM orders
WHERE customer_ref IS NULL
ORDER BY id
LIMIT 1000
);- Loop until zero rows.
- Sleep between batches.
- Watch replication lag and lock waits.
- Make it resumable and idempotent.
- Do not run one
UPDATEover millions of rows in a single transaction.
7. Set NOT NULL without a long lock
- Add a
CHECK (col IS NOT NULL) NOT VALID. - Validate it.
- Set
NOT NULL. PostgreSQL can use the validated check to avoid a full scan. - Drop the temporary check.
8. Plan rollback
- Can the old application run against the new schema? If not, the migration is not backward compatible.
- Is the down migration safe, or does it lose data?
- If data was rewritten, is there a backup or a reversible mapping?
- What is the exact command and the expected duration?
- Who runs it, and how is it verified?
A migration that cannot be rolled back needs a forward-fix plan and an explicit approval.
9. Deploy and observe
- Run the migration before the code that needs it, when the change is additive.
- Run destructive steps after the old code is fully gone.
- Watch: lock waits, replication lag, error rate, query latency, disk usage.
- Keep a dashboard open for the migration window.
- Stop on the first sign of saturation.
Examples
High risk:
ALTER TABLE events ALTER COLUMN payload TYPE jsonb USING payload::jsonb;Rewrites the table under an exclusive lock. On a large table, writes stop for the duration.
Safer plan:
- Add
payload_jsonb jsonb. - Dual-write from the application.
- Backfill in batches.
- Switch reads.
- Drop the old column in a later release.
Also risky:
CREATE INDEX idx_events_created ON events (created_at);Blocks writes while it builds. Use CREATE INDEX CONCURRENTLY and run outside a transaction.
Guardrails
- Do not run a migration on production without a measured table size and traffic estimate.
- Do not combine additive and destructive changes in one deploy.
- Do not use
CREATE INDEX CONCURRENTLYinside a transaction wrapper. - Do not backfill in a single unbounded update.
- Do not assume a lock is brief because the statement is short.
- Do not drop a column before the old code stops reading it.
- Do not rely on a down migration that loses data. Say so explicitly.
- Do not test on an empty table and infer production behavior.
Verification checklist
- Engine and version confirmed.
- Table size and write rate recorded.
- Every statement classified by lock.
- Additive changes separated from destructive ones.
- Index builds use the concurrent form where available.
- Constraints added
NOT VALIDand validated separately. - Backfill batched, idempotent, resumable.
- Old and new code can run together.
- Rollback path tested or explicitly declared unsafe.
- Monitoring window and stop conditions defined.
References
references/migration-checklist.mdcontains a statement-by-statement review sheet and a rollout timeline.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

