From ftitos-claude-code
Database migration best practices for schema changes, data migrations, rollbacks, and zero-downtime deployments across PostgreSQL and common ORMs.
npx claudepluginhub nassimbf/ftitos-claude-codeThis skill uses the workspace's default tool permissions.
Safe, reversible database schema changes for production systems.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Share bugs, ideas, or general feedback.
Safe, reversible database schema changes for production systems.
-- Good: Nullable column, no lock
ALTER TABLE users ADD COLUMN avatar_url TEXT;
-- Good: Column with default (Postgres 11+ is instant)
ALTER TABLE users ADD COLUMN is_active BOOLEAN NOT NULL DEFAULT true;
-- Bad: NOT NULL without default (requires full rewrite, locks table)
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
CREATE INDEX CONCURRENTLY idx_users_email ON users (email);
Use the expand-contract pattern:
-- Step 1: Add new column
ALTER TABLE users ADD COLUMN display_name TEXT;
-- Step 2: Backfill data (separate migration)
UPDATE users SET display_name = username WHERE display_name IS NULL;
-- Step 3: Update application code to read/write both columns
-- Step 4: Drop old column
ALTER TABLE users DROP COLUMN username;
-- Bad: Updates all rows in one transaction
UPDATE users SET normalized_email = LOWER(email);
-- Good: Batch update
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 $$;
npx prisma migrate dev --name add_user_avatar # Create migration
npx prisma migrate deploy # Apply in production
npx drizzle-kit generate # Generate migration
npx drizzle-kit migrate # Apply migrations
python manage.py makemigrations # Generate from model changes
python manage.py migrate # Apply migrations
migrate create -ext sql -dir migrations -seq add_user_avatar
migrate -path migrations -database "$DATABASE_URL" up
Phase 1: EXPAND
- Add new column/table (nullable or with default)
- Deploy: app writes to BOTH old and new
- Backfill existing data
Phase 2: MIGRATE
- Deploy: app reads from NEW, writes to BOTH
- Verify data consistency
Phase 3: CONTRACT
- Deploy: app only uses NEW
- Drop old column/table in separate migration
| Anti-Pattern | Better Approach |
|---|---|
| Manual SQL in production | Always use migration files |
| Editing deployed migrations | Create new migration instead |
| NOT NULL without default | Add nullable, backfill, then constrain |
| Inline index on large table | CREATE INDEX CONCURRENTLY |
| Schema + data in one migration | Separate migrations |
| Dropping column before removing code | Remove code first, drop next deploy |