원클릭으로
arib-check-migrate
Check | Database migration safety review - risk classification, lock analysis, rollback verification
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Check | Database migration safety review - risk classification, lock analysis, rollback verification
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | arib-check-migrate |
| argument-hint | <migration-file> |
| description | Check | Database migration safety review - risk classification, lock analysis, rollback verification |
Database migrations are the #1 cause of production outages. A single badly-written migration can lock an entire table (100M rows = hours of downtime), corrupt data, or become irreversible.
This skill classifies migration risk before execution, calculates lock duration, detects dangerous patterns, and verifies rollback procedures. It acts as a safety gate: migrations are APPROVED, APPROVED WITH CONDITIONS, or BLOCKED.
Key decisions:
Review database migrations for safety before execution. Classifies risk, detects dangerous operations, verifies rollback plan, and produces a migration safety report.
User types /arib-check-migrate [target]
Examples:
/arib-check-migrate - Review all pending migrations/arib-check-migrate 20260418_add_user_status - Specific migration/arib-check-migrate prisma/migrations/ - All Prisma migrations/arib-check-migrate --all - Comprehensive schema auditBad (locks table for minutes/hours):
ALTER TABLE users ALTER COLUMN age TYPE VARCHAR(3);
For 100M rows, this blocks read/write for 30+ minutes.
Good (instant):
-- Step 1: Add new column
ALTER TABLE users ADD COLUMN age_str VARCHAR(3);
-- Step 2: Copy data (in batches if large)
UPDATE users SET age_str = age::text;
-- Step 3: Application uses both columns
-- Step 4: After verification, drop old column
ALTER TABLE users DROP COLUMN age;
Bad (locks table):
CREATE INDEX idx_users_email ON users(email);
Blocks writes while building index.
Good (concurrent, no locks):
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
-- Takes longer but table remains writable
Bad (fails if rows exist):
ALTER TABLE users ADD COLUMN is_verified BOOLEAN NOT NULL;
-- ERROR: column "is_verified" contains nulls
Good (safe):
-- Step 1: Add column with default
ALTER TABLE users ADD COLUMN is_verified BOOLEAN NOT NULL DEFAULT false;
-- Step 2: Update any existing rows if needed
-- Step 3: Remove default if application handles it
ALTER TABLE users ALTER COLUMN is_verified DROP DEFAULT;
Bad (irreversible):
DROP TABLE legacy_users; -- If only backup is 3 days old, you've lost recent data
Good (safe):
-- Step 1: Rename, don't drop
ALTER TABLE legacy_users RENAME TO legacy_users_backup_20260419;
-- Step 2: Monitor for 1 week
-- Step 3: Only then drop
DROP TABLE legacy_users_backup_20260419;
Bad (corrupts data):
UPDATE users SET status = 'active'; -- Sets ALL users to active!
Good (safe):
UPDATE users SET status = 'active' WHERE status IS NULL;
-- Affected rows: 1,234 (visible before committing)
Bad (unintended deletions):
ALTER TABLE orders ADD CONSTRAINT fk_users
FOREIGN KEY (user_id) REFERENCES users(id)
ON DELETE CASCADE; -- Deletes all orders when user is deleted!
Good (explicit):
ALTER TABLE orders ADD CONSTRAINT fk_users
FOREIGN KEY (user_id) REFERENCES users(id)
ON DELETE RESTRICT; -- Prevents deletion if orders exist
-- Application handles cleanup explicitly
Table Size × Operation = Lock Duration
Operation | 1K Rows | 100K Rows | 10M Rows | 100M Rows
-----------|---------|-----------|----------|----------
ADD COLUMN (default) | < 1ms | < 1ms | < 1ms | < 1ms ✓
CREATE INDEX | 10ms | 100ms | 5s | 2min
ALTER TYPE | 100ms | 1s | 1min | 30min
DROP INDEX | < 1ms | < 1ms | 100ms | 1s
DELETE (all) | 10ms | 100ms | 10s | 2min
UPDATE (all) | 20ms | 200ms | 20s | 5min
CREATE UNIQUE INDEX | 50ms | 500ms | 20s | 10min
VACUUM FULL | 1s | 5s | 5min | 1hour
Test procedure:
SELECT COUNT(*) FROM table_nameSELECT * FROM pg_stat_activityBefore running migration on production:
# Step 1: Run on exact staging replica
pg_dump prod_database | psql staging_database
psql staging_database < migration.sql
# Step 2: Measure performance
\timing on
SELECT COUNT(*) FROM affected_table; # Verify no corruption
# Step 3: Verify application still works
npm test -- --integration-db staging
# Step 4: Test rollback
psql staging_database < rollback.sql
SELECT COUNT(*) FROM affected_table; # Same count as original?
## Migration: 20260419_add_user_preferences
### Risk Assessment: LOW
**Operations:**
1. ADD COLUMN user_preferences JSONB DEFAULT '{}' - Lock: < 1ms
2. CREATE INDEX CONCURRENTLY idx_user_pref - Lock: 0 (concurrent)
**Affected Table:** users (42.3M rows)
- Lock duration: < 1 second
- No data loss risk
- Backwards compatible
**Rollback:** DOWN migration tested and works
- Removes column cleanly
- Tested on staging with identical data size
**Verdict:** APPROVED - Safe to deploy during business hours
**Deployment:** Run during normal traffic, no maintenance window needed
**Monitoring:** Watch database connections for 5 minutes post-deploy
## Migration: 20260419_alter_order_amount_type
### Risk Assessment: HIGH
**Operations:**
1. ALTER TABLE orders ALTER COLUMN amount TYPE NUMERIC(12,2)
- Current type: VARCHAR(255)
- Table size: 87.1M rows
- Estimated lock: 12-18 minutes
- Data rewrite: Yes
**Conditions for Approval:**
1. REQUIRED: Deploy during maintenance window (2am-4am PT)
2. REQUIRED: Team on-call with direct DB access
3. REQUIRED: Full backup taken and verified
4. REQUIRED: Application tested with new type in staging
5. REQUIRED: Rollback plan documented and tested
**Lock Analysis:**
- 87.1M rows × ALTER TYPE = 15-minute lock
- Users will see: "Database maintenance, back in 20 minutes"
- Status page: Announced 48h in advance
- Support team: Briefed on expected downtime
**Rollback:** Available - uses old table rename trick
- Rename orders to orders_new
- Restore from backup
- Max data loss: 15 minutes
**Verdict:** APPROVED WITH CONDITIONS
**Deployment Time:** 2026-04-20 2:00 AM PT (maintenance window)
**Estimated Duration:** 25 minutes (15 min migration + 10 min verification)
## Migration: 20260419_drop_legacy_users_table
### Risk Assessment: CRITICAL - BLOCKED
**Blocking Issues:**
1. IRREVERSIBLE OPERATION
- DROP TABLE legacy_users has no down migration
- If data corruption discovered post-deploy, cannot recover
- Only backup is 48 hours old (data loss risk)
2. INSUFFICIENT BACKUP VERIFICATION
- Backup not tested for restore
- No verification that backup is complete
- No documented recovery time
3. MISSING IMPACT ANALYSIS
- No check for foreign key references
- No verification that no application code reads this table
- 2.1M rows of data with unknown lineage
4. NO MIGRATION DOWN PATH
- Cannot rollback if needed
- Migration assumes data not needed - not verified
**Required Fixes:**
1. FIX: Create backup and verify restore works (30 min)
- Run: `pg_dump prod_database | pg_restore test_restore`
- Verify: Same row counts in both databases
2. FIX: Add safe DOWN migration (15 min)
- Instead of DROP: RENAME table to legacy_users_archive_20260419
- Only DROP after 1 month of monitoring
- Document rollback procedure
3. FIX: Verify no code references this table (30 min)
- grep -r "legacy_users" src/
- Check all migrations for foreign keys
- Check all application queries
4. FIX: Document why table can be dropped (15 min)
- Who uses it? Nobody
- When can we drop it? After monitoring period
- What happens if we rollback? [Procedure]
**Decision:** BLOCKED - Rewrite migration using safe pattern
**Timeline:** Fix by 2026-04-21, re-submit for review
| Mistake | Problem | Fix |
|---|---|---|
| No DOWN migration | Can't rollback | Every UP needs matching DOWN |
| ALTER TYPE directly | Table locked 30 min | Use ADD → COPY → DROP pattern |
| ADD NOT NULL no default | Fails if rows exist | Add default, then alter if needed |
| DROP without backup | Data loss is permanent | Always backup, verify restore |
| CREATE INDEX not CONCURRENTLY | Table locked during index build | Add CONCURRENTLY for production |
| No pre-check for locks | Surprised by long lock time | Test on staging first |
| UPDATE without WHERE | Corrupts all rows | Test migration query on copy first |
| Foreign key CASCADE delete | Unintended deletions | Use RESTRICT, handle in app |
| Deploying during peak hours | More downtime, more users affected | Always use maintenance window for locks |
| No application testing | Code breaks after schema change | Test new schema against application |
Read .claude/agents/database-guardian.md and follow the 8-step Migration Safety Protocol.
Identify which migration tool is used:
prisma/migrations/)migrations/)migrations/ or db/migrate/)src/migrations/)*/migrations/)alembic/versions/)Migrations/)Read all pending/target migration files. For each migration, extract:
For each operation, classify as LOW / MEDIUM / HIGH / CRITICAL per the Database Guardian protocol.
If connected to a database, check row counts for affected tables:
SELECT relname, n_live_tup FROM pg_stat_user_tables WHERE relname IN ('affected_tables');
If not connected, ask user for approximate table sizes.
Scan for:
Confirm:
Produce the Migration Safety Report with:
Memory | Code-graph subsystem — a native lightweight IMPORT graph (which file imports which, god-node candidates) built with ripgrep/grep, so a large monorepo can be navigated by structure instead of re-grepping every session. build/refresh/query. Honest scope: structural import graph, NOT semantic (no call-graph/type resolution); no Graphify dependency. Loads ON DEMAND only (zero always-on tokens). ADR-034.
Dev | Over-engineering review — reads a diff/module and returns a delete-list of bloat (single-use abstractions, premature generalization, speculative config, dead options) WITHOUT stripping legitimate structure. Advisory: returns recommendations, never auto-deletes. The on-demand companion to the ponytail-lite tripwire hook. Authored natively (no Ponytail dependency). ADR-033.
Wave | Pre-wave requirement lock — Act 1 derives the requirements from the codebase + memory (an honest grill, not guesswork), Act 2 hands the locked plan to an independent model (Codex) to tear apart until sign-off. Produces waves/<id>/PLAN.md + PLAN-REVIEW-LOG.md. Auto-chained idempotently from /arib-wave-start. If Act 2 can't run (no Codex), the wave proceeds but HOLDS MERGE for a human. Absorbs grill-me-codex (ADR-032).
Wave | Start a multi-session delivery wave — branch, plan, parallel architect+planner
Engine | Command the engineering team to deliver a known goal — dispatches the engineer-manager to decompose → dispatch specialists (parallel where safe) → integrate → reconcile (verification-agent) → merge gate. Scales its own reach: runs inline for a bounded goal, escalates to a parallel Workflow for a broad one, and paces under /loop for a multi-turn campaign — only when it needs to. Use when a goal needs a coordinated TEAM, not one specialist. Sibling of /arib-engine: the engine DISCOVERS its own backlog; /arib-build EXECUTES a goal you hand it.
Stack | NestJS architecture & patterns reference — modules/providers/DI, DTO+validation, guards/interceptors/pipes/filters, config, async lifecycle, testing, and the security + performance pitfalls that bite at scale. Use when building or reviewing a NestJS backend. Composes with security-auditor (OWASP), database-guardian (TypeORM/Prisma migrations), and performance (N+1). Authored natively (the ECC graft was unsourceable; this is CCM's own, MIT).