zeno-database-migrations
Skill1 starsChanged 8 days agoMITView on GitHub
Database migration safety across PostgreSQL, MySQL, and common ORMs (Prisma, Drizzle, Kysely, Django, golang-migrate) — schema changes, backfills, rollbacks, and zero-downtime expand-contract deployments. Use when creating or altering a table, adding an index or column on a live table, planning a data backfill, or wiring up migration tooling for a new project. For the underlying SQL/index/schema patterns themselves see zeno-postgres-patterns; this skill covers how to roll a change out safely, not what the schema should look like. Trigger on: "write a migration", "add a column to this table", "zero-downtime schema change", "backfill this data", "rollback a migration", "expand-contract", "rename a column safely", "migration tooling".
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 1 is distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 44
Before applying any migration:
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 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
Database Migrations
Safe, reversible schema and data changes for production systems — across raw SQL and the common ORM migration tools.
When to reach for this skill
- Creating or altering a table
- Adding or removing a column or index on a table that already has production data
- Running a data migration (backfill, transform)
- Planning a zero-downtime schema change
- Setting up migration tooling for a new project
Core principles
- Every change is a migration file — never alter a production database by hand.
- Migrations are forward-only in production — a rollback is a new forward migration that undoes the previous one, not an edit to history.
- Schema and data migrations are separate — never mix DDL and DML in one file; a long-running data transform inside a schema-change transaction turns a fast DDL change into a multi-minute lock.
- Test against production-sized data — a migration that's instant on 100 rows can lock a table for minutes on 10M.
- Migrations are immutable once deployed — never edit a migration that has already run in production; environments drift the moment you do.
Migration safety checklist
Before applying any migration:
- Has both an UP and a DOWN, or is explicitly marked irreversible
- No full-table lock on a large table (use concurrent operations)
- New columns are nullable or have a default (never add
NOT NULLbare) - Indexes on existing tables are created concurrently, not inline
- Data backfill is its own migration, separate from the schema change
- Tested against a copy of production-scale data
- Rollback plan is documented
PostgreSQL patterns
-- GOOD: nullable column, no lock
ALTER TABLE users ADD COLUMN avatar_url TEXT;
-- GOOD: column with a default (Postgres 11+ does this instantly, no table rewrite)
ALTER TABLE users ADD COLUMN is_active BOOLEAN NOT NULL DEFAULT true;
-- BAD: NOT NULL with no default on an existing table — forces a full rewrite and locks
ALTER TABLE users ADD COLUMN role TEXT NOT NULL;-- BAD: blocks writes on large tables
CREATE INDEX idx_users_email ON users (email);
-- GOOD: non-blocking, allows concurrent writes
-- Note: CONCURRENTLY cannot run inside a transaction block — most migration
-- tools need special handling to skip wrapping it in one
CREATE INDEX CONCURRENTLY idx_users_email ON users (email);Renaming a column — never rename directly in production; use expand-contract:
-- 1. Add the new column
ALTER TABLE users ADD COLUMN display_name TEXT;
-- 2. Backfill (separate migration)
UPDATE users SET display_name = username WHERE display_name IS NULL;
-- 3. Deploy application code that reads/writes both columns, then only the new one
-- 4. Once nothing reads the old column, drop it (separate migration)
ALTER TABLE users DROP COLUMN username;Large data migrations — batch instead of updating every row in one transaction:
DO $$
DECLARE
batch_size INT := 10000;
rows_updated INT;
BEGIN
LOOP
UPDATE users
SET normalized_email = LOWER(email)
WHERE id IN (
SELECT id FROM users
WHERE normalized_email IS NULL
LIMIT batch_size
FOR UPDATE SKIP LOCKED
);
GET DIAGNOSTICS rows_updated = ROW_COUNT;
EXIT WHEN rows_updated = 0;
COMMIT;
END LOOP;
END $$;ORM tooling reference
Prisma:
npx prisma migrate dev --name add_user_avatar # create + apply in dev
npx prisma migrate deploy # apply pending in production
npx prisma migrate dev --create-only --name x # empty migration for hand-written SQLPrisma can't express CREATE INDEX CONCURRENTLY — write that SQL by hand inside a
--create-only migration.
Drizzle:
npx drizzle-kit generate # generate migration from schema changes
npx drizzle-kit migrate # apply migrations
npx drizzle-kit push # push schema directly, dev only — no migration fileKysely (kysely-ctl):
kysely migrate make add_user_avatar
kysely migrate latest
kysely migrate downMigration files always type the database as Kysely<any>, never the app's typed DB
interface — a migration is frozen in time and must not depend on today's schema types.
Django:
python manage.py makemigrations
python manage.py migrate
python manage.py makemigrations --empty app_name -n description # custom SQL/dataSeparateDatabaseAndState removes a field from the Django model without dropping the
database column in the same migration — useful for the "stop referencing, then drop"
sequence:
migrations.SeparateDatabaseAndState(
state_operations=[migrations.RemoveField(model_name="user", name="legacy_field")],
database_operations=[], # DB column stays until a later migration drops it
)golang-migrate:
migrate create -ext sql -dir migrations -seq add_user_avatar
migrate -path migrations -database "$DATABASE_URL" up
migrate -path migrations -database "$DATABASE_URL" down 1
migrate -path migrations -database "$DATABASE_URL" force VERSION # fix a dirty stateZero-downtime strategy: expand-contract
Phase 1: EXPAND
- Add new column/table (nullable or with a default)
- Deploy app that writes to BOTH old and new
- Backfill existing rows
Phase 2: MIGRATE
- Deploy app that reads from NEW, still writes to BOTH
- Verify data consistency between old and new
Phase 3: CONTRACT
- Deploy app that only uses NEW
- Drop the old column/table in a separate, later migrationExample timeline for a status-field rename:
Day 1: Migration adds new_status column (nullable); deploy app v2 writes both columns
Day 2: Backfill migration runs for existing rows
Day 3: Deploy app v3 — reads from new_status only
Day 7: Migration drops the old status columnAnti-pattern quick reference
| Anti-pattern | Instead |
| Manual SQL run directly against production | Always go through a migration file — no audit trail otherwise |
| Editing a migration that already ran in production | Write a new migration; edited history causes environment drift |
NOT NULL column added with no default | Add nullable, backfill, then add the constraint in a later migration |
Inline CREATE INDEX on a large existing table | CREATE INDEX CONCURRENTLY |
| Schema change and data backfill in one migration | Split into separate migrations — different lock profiles, different rollback needs |
| Dropping a column before removing app code that reads it | Ship the code change first; drop the column in the following deploy |
| Renaming a column directly | Expand-contract: add new, backfill, cut over, drop old |
Pre-flight checklist — run before delivering any database migration
- Migration file has both UP and DOWN, or is explicitly marked irreversible
- No full-table lock introduced on a table with production data
- New columns are nullable or carry a default — no bare
NOT NULLadd - Indexes on existing tables are created concurrently
- Schema change and data backfill are separate migrations
- Tested against production-scale data, not a handful of rows
- Rollback plan is written down before this ships
- Column/table removal follows expand-contract, not a direct drop
In this repository91 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- goSkill
- json-standardsSkill
- pythonSkill
- rabbitmqSkill
- rSkill
- rustSkill
- code-standardsSkill
- software-architectureSkill
- swiftSkill
- typescript-code-standardsSkill
- architecture-documentation-and-decision-recordsSkill
- business-requirements-standardsSkill
- developer-handoff-standardsSkill
- development-planning-standardsSkill
- document-writer-reviewerSkill
- feature-planning-standardsSkill
- functional-specification-standardsSkill
- non-functional-requirements-standardsSkill
- operations-standardsSkill
- product-requirements-standardsSkill
- documentationSkill
- solution-architecture-standardsSkill
- technical-requirements-standardsSkill
- documenting-codeSkill
- github-circleci-standardsSkill
- graphifySkill
- puml-diagramsSkill
- software-architecture-progressionSkill
- testingSkill
- software-test-specificationSkill
- zeno-councilSkill
- zeno-debugSkill
- zeno-insightSkill
- zenoSkill
- zeno-stackSkill
- zeno-accessibilitySkill
- zeno-angular-developerSkill
- zeno-api-designSkill
- zeno-browser-qaSkill
- zeno-build-resolverSkill
- zeno-codebase-onboardingSkill
- zeno-code-tourSkill
- zeno-comment-analysisSkill
- zeno-config-gcSkill
- zeno-context-budgetSkill
- zeno-context-compactionSkill
- zeno-cost-reportSkill
- zeno-deep-researchSkill
- zeno-deployment-patternsSkill
- zeno-design-systemSkill
- zeno-docker-patternsSkill
- zeno-e2e-testingSkill
- zeno-error-handlingSkill
- zeno-find-skillsSkill
- zeno-frontend-designSkill
- zeno-github-opsSkill
- zeno-git-workflowSkill
- zeno-harness-optimizerSkill
- The first 60 shown.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
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.

