Community寫作與編輯github.com

windchillscalanthes-ship-it/safe-migrations

Catch the database migration that locks your table before it ships. A Claude Agent Skill that reviews migrations for downtime risks and rewrites them into safe, zero-downtime steps. Postgres & MySQL, any framework.

safe-migrations 是什麼?

safe-migrations is a Claude Code agent skill that catch the database migration that locks your table before it ships. A Claude Agent Skill that reviews migrations for downtime risks and rewrites them into safe, zero-downtime steps. Postgres & MySQL, any framework.

相容平台Claude Code~Codex CLI~Cursor
npx skills add windchillscalanthes-ship-it/safe-migrations

Installed? Explore more 寫作與編輯 skills: steipete/notion, affaan-m/seo, affaan-m/brand-voice · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Safe Migrations

Catch the schema changes that quietly lock a table or rewrite it on disk — and turn them into safe, zero-downtime migrations before they reach production.

A single innocent-looking line (SET NOT NULL, CREATE INDEX, ADD FOREIGN KEY, a column with a DEFAULT) can take an ACCESS EXCLUSIVE lock or force a full-table rewrite. On a large, busy table that means every read and write queues behind the migration — an outage. This skill reviews a migration, explains exactly what would lock and why, and produces a safe rewrite.

When to use this

  • You are writing a new migration and want it checked before commit.
  • You are reviewing a migration in a PR/diff.
  • You are about to run a migration against a table that already has rows.
  • Someone asks "is this migration safe?", "will this lock the table?", or "how do I do this with zero downtime?".

If the target table is empty or tiny (a fresh table created in the same migration, a config table with a handful of rows), most of these risks don't apply — say so and don't over-engineer.

Workflow

  1. Establish context. Identify the engine (PostgreSQL vs MySQL/MariaDB and, if you can, the major version) and the framework. Version matters: several operations are safe on Postgres 11/12+ but not before. If the version is unknown, flag the operations whose safety is version-dependent.

  2. Locate the operations. Read each migration file and list every schema or data operation it performs. One migration often bundles several.

  3. Classify each operation against the risk catalog below. Assign a severity:

    • 🔴 Unsafe — will take a blocking lock, rewrite the table, or break a running app version. Must be rewritten.
    • 🟡 Caution — safe only under conditions (small table, specific engine version, low traffic, correct settings). Call out the condition.
    • 🟢 Safe — metadata-only or otherwise non-blocking. No change needed.
  4. Rewrite the unsafe ones. For each 🔴/🟡, give:

    • the concrete risk (which lock, what it blocks, rough impact),
    • a safe rewrite as runnable steps (SQL and, when a framework is in use, the idiomatic framework code), and
    • the deploy sequence when app code must change in lockstep (see Expand / Contract below).
  5. Report using the output template at the end. Lead with the unsafe items.

For engine- and framework-specific depth, load the reference files on demand:

  • reference/postgres.md — PostgreSQL locks, versions, and safe rewrites (the most detailed).
  • reference/mysql.md — MySQL/MariaDB online DDL, ALGORITHM/LOCK, gh-ost.
  • reference/patterns.md — engine-agnostic patterns: expand/contract, batched backfills, lock_timeout, N-1 compatibility.
  • reference/frameworks.md — how to express concurrent indexes, disable the DDL transaction, and split state vs. schema in each framework.

Read the reference file relevant to the migration in front of you rather than guessing version-specific behavior.

Risk catalog (the high-frequency offenders)

Postgres-centric; MySQL differences are in reference/mysql.md. "Rewrite" = the whole table is copied on disk; "scan" = full read of the table; both are held under a lock that blocks concurrent traffic.

#OperationWhy it's dangerousSafe approach
1Add column with a DEFAULTVolatile default (e.g. gen_random_uuid(), random()) rewrites the table under ACCESS EXCLUSIVE. Any default rewrites on Postgres < 11.Add the column nullable with no default → ALTER … SET DEFAULT (new rows only) → backfill existing rows in batches → add NOT NULL (row 2).
2SET NOT NULL on an existing columnFull-table scan under ACCESS EXCLUSIVE to verify no nulls — blocks reads and writes.Add CHECK (col IS NOT NULL) NOT VALIDVALIDATE CONSTRAINT (non-blocking) → SET NOT NULL (Postgres 12+ reuses the validated check and skips the scan) → optionally drop the check.
3CREATE INDEX (non-concurrent)Holds a SHARE lock that blocks writes for the entire build.CREATE INDEX CONCURRENTLY — must run outside a transaction; can leave an INVALID index if it fails (drop and retry).
4DROP INDEXACCESS EXCLUSIVE on the table.DROP INDEX CONCURRENTLY (Postgres 9.2+).
5Add FOREIGN KEYLocks both tables and scans the child to validate existing rows.Add the constraint NOT VALID (fast, no scan) → VALIDATE CONSTRAINT in a separate step (takes only SHARE UPDATE EXCLUSIVE).
6Add UNIQUE constraintBuilds a unique index under a blocking lock.CREATE UNIQUE INDEX CONCURRENTLYALTER TABLE … ADD CONSTRAINT … UNIQUE USING INDEX <name> (attaches the existing index, fast).
7Add CHECK constraintScans the table under ACCESS EXCLUSIVE.ADD CONSTRAINT … CHECK (…) NOT VALIDVALIDATE CONSTRAINT.
8ALTER COLUMN … TYPEUsually rewrites the table and its indexes under ACCESS EXCLUSIVE. (Exceptions: widening/removing a varchar length limit is metadata-only.)Expand/contract: add a new column, dual-write + backfill, switch reads, drop the old column.
9RENAME column/tableThe DDL is instant, but it breaks the running app version the moment it commits.Never rename in place on a live app. Expand/contract: add new name, dual-write, backfill, switch reads, drop old — across two deploys.
10DROP COLUMNApp code still referencing it (including SELECT *) breaks; the data is gone (hard to roll back).Contract phase only: ship app code that stops using the column first, then drop in a later migration.
11Backfill in one big UPDATELong-running transaction → lock contention, table bloat, replication lag.Batch by primary-key ranges with a commit between batches; keep each batch short. Do it outside the schema migration.
12No lock_timeout / statement_timeoutEven an instant DDL needs a brief ACCESS EXCLUSIVE lock. If it queues behind a long query, it blocks every query that arrives after it — a cascading stall.Set a low lock_timeout (and statement_timeout) before DDL so the migration fails fast instead of taking the site down.

Guiding principles

  • Expand / contract (parallel change). Any change that isn't backward compatible must be split so the database is simultaneously compatible with the old and new app versions during a rollout. Expand (add, backward compatible) → migrate data / dual-write → contract (remove) — usually across two or more deploys. This is the backbone of renames, type changes, splits, and merges.
  • One logical change per migration. Easier to review, reason about, and roll back. Never mix a schema change with a large data backfill in the same transaction.
  • N-1 compatibility. During a deploy, both versions of the app run at once. The schema must serve both.
  • Separate schema from data. Structural DDL and row backfills belong in different steps (and often different deploys).
  • Right-size the advice. Empty/tiny/brand-new tables don't need any of this. Don't turn a two-line change into a five-step dance when the table has 12 rows.

Output template

## Migration Safety Review

**Engine:** <e.g. PostgreSQL 14>  ·  **Framework:** <e.g. Rails>  ·  **Files reviewed:** <n>

### 🔴 <file> — UNSAFE
**Operation:** <what it does>
**Risk:** <which lock / rewrite, what it blocks>
**Likely impact:** <e.g. "global lock on `users` for the duration of a full scan; on a 40M-row table, seconds-to-minutes of blocked traffic">
**Safe rewrite:**
<runnable steps — SQL and/or framework code>
**Deploy sequence:** <only if app code must change in lockstep>

### 🟡 <file> — CAUTION
**Operation / Risk / Condition / Recommendation**

### 🟢 <file> — SAFE
<one line: why it's safe, no action needed>

---
**Summary:** <n unsafe, n caution, n safe.> <one-line bottom line.>

Always show the safe rewrite as something the user can copy and run — not just a description of what to do.

相關技能

steipete/notion

Notion CLI/API for pages, Markdown content, data sources, files, comments, search, Workers, and raw API calls.

community

affaan-m/seo

Audit, plan, and implement SEO improvements across technical SEO, on-page optimization, structured data, Core Web Vitals, and content strategy. Use when the user wants better search visibility, SEO remediation, schema markup, sitemap/robots work, or keyword mapping.

community

affaan-m/brand-voice

Build a source-derived writing style profile from real posts, essays, launch notes, docs, or site copy, then reuse that profile across content, outreach, and social workflows. Use when the user wants voice consistency without generic AI writing tropes.

community

affaan-m/crosspost

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

community

affaan-m/x-api

X/Twitter API integration for posting tweets, threads, reading timelines, search, and analytics. Covers OAuth auth patterns, rate limits, and platform-native content posting. Use when the user wants to interact with X programmatically.

community

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

community