agentleFS
auralshinauralshin/coding-skills/skills/expand-contract/SKILL.md

expand-contract

Skill0 starsChanged 3 months agoMITView on GitHub

This skill should be used when the user asks to "rename a column without downtime", "drop a column safely", "add NOT NULL to a live table", "add an index in production", "change an API response shape", "change an event format", or asks how to run a zero-downtime migration. Provides the expand-migrate-contract sequence and per-operation recipes.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

8 rules. About 0 are common, 6 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    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
  1. Distinctive:

    Expand: add the new thing. Additive only.

    line 16
  2. Distinctive:

    Migrate: write to both, backfill, move readers to the new thing, stop writing the old.

    line 17
  3. Distinctive:

    Contract: delete the old thing once nothing reads or writes it.

    line 18
  4. Very specific:

    Never hold one long transaction: it blocks vacuum and grows replication lag.

    line 74
  5. Distinctive:

    Never repurpose a name. A field whose meaning changed is undetectable to consumers, while a field that disappeared is a clear error.

    line 89
  6. Very specific:

    Never SELECT * in application code, since a new column changes the result shape.

    line 114
  7. Distinctive:

    Never rely on column order or JSON key order.

    line 115
  8. Distinctive:

    Keep migrations in the repository with the code that needs them.

    line 117

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Expand and contract

During any rolling deploy, blue-green cutover, or canary, old and new code run simultaneously against the same database, often for minutes and after a rollback for hours.

Every intermediate state must be compatible with both the old and the new version.

A migration correct only after every process restarts causes an outage. This makes ALTER TABLE ... RENAME COLUMN unsafe regardless of how fast it runs: the moment it commits, running old code breaks.

Three phases

  1. Expand: add the new thing. Additive only. Old code untouched and unaware.
  2. Migrate: write to both, backfill, move readers to the new thing, stop writing the old.
  3. Contract: delete the old thing once nothing reads or writes it.

Each phase is at least one deploy, independently deployable and revertible. The gap between phases is where problems surface, so do not compress it.

Schema recipes

Rename a column: six steps

#StepType
1ADD COLUMN new_name nullable, no rewriting defaultschema
2Write both columns on every insert and updatecode
3Backfill new_name from old_name in batchesscript
4Read from new_name, keep writing bothcode
5Stop writing old_namecode
6DROP COLUMN old_nameschema

Stopping between any two steps leaves a correct system. Wait between steps 4 and 6 long enough to be confident there will be no rollback.

Add a NOT NULL column

ALTER TABLE users ADD COLUMN plan text;                    -- 1. nullable, instant on PG 11+

-- 2. code writes it on every new row

UPDATE users SET plan = 'free' WHERE plan IS NULL AND id BETWEEN $1 AND $2;   -- 3. batched

ALTER TABLE users ADD CONSTRAINT users_plan_nn CHECK (plan IS NOT NULL) NOT VALID;  -- 4
ALTER TABLE users VALIDATE CONSTRAINT users_plan_nn;       -- takes only SHARE UPDATE EXCLUSIVE
ALTER TABLE users ALTER COLUMN plan SET NOT NULL;          -- PG 12+ uses the constraint, no scan

Other operations

ChangeSafe approach
Add an indexCREATE INDEX CONCURRENTLY in Postgres (cannot run inside a transaction), online DDL in MySQL 8, or gh-ost
Change a column typeTreat as a rename: new column, dual-write, backfill, switch, drop
Add a foreign keyADD CONSTRAINT ... NOT VALID, then VALIDATE CONSTRAINT separately
Drop a columnStop reading (deploy), stop writing (deploy), then drop. Remove it from any SELECT * path first
Split a tableDual-write both shapes, backfill, migrate readers, stop writing the old
Add a volatile defaultAdd nullable, backfill in batches, then set the default for new rows

The lock queue trap

In Postgres a DDL statement waiting for a lock blocks every query arriving behind it, including plain SELECTs. One slow ALTER behind one long transaction stalls the whole table. Always cap the wait:

SET lock_timeout = '3s';
ALTER TABLE ...;   -- fails fast instead of building a queue

Backfills

  • Batch a few thousand rows per statement with a pause between batches.
  • Never hold one long transaction: it blocks vacuum and grows replication lag. See mvcc-and-bloat.
  • Make the loop resumable and idempotent by tracking the last id processed. See keyset-pagination.
  • Throttle on replica lag rather than wall-clock time.

API and event changes

ChangeSafe approach
Add a fieldAdd it. Consumers must ignore unknown fields, and the contract must say so
Rename a fieldEmit both, wait for consumers, remove the old
Remove a fieldInstrument first, deprecate, then remove
Change a field's typeNew field with a new name. Never change a type in place
Change semanticsNew field. Repurposing a name is the worst option because nothing breaks loudly, it breaks quietly and wrongly
Tighten validationLog what would have been rejected for a full cycle before rejecting

Never repurpose a name. A field whose meaning changed is undetectable to consumers, while a field that disappeared is a clear error. Prefer the loud failure.

For queues, add one constraint: a queue holds messages written by the previous version for its full retention window. Consumers must handle both shapes until that window passes after the last old producer stops.

Instrument before deleting

Prove nothing uses the old thing:

  • API field: count reads per field, tagged by client, over a full business cycle including monthly and quarterly jobs
  • Column: check query logs and grep every repository, not only this one
  • Endpoint: metrics by caller identity, not total request count

"Nobody uses it" without a metric means "nobody I asked". Batch jobs appear at month end, right after the column is dropped. See hyrums-law.

Tools

ToolPurpose
squawkLints Postgres migration SQL for unsafe operations
strong_migrationsRails gem blocking unsafe migrations and printing the safe recipe
gh-ost, pt-online-schema-changeOnline schema change for MySQL
atlas, sqldefDeclarative schema with migration linting

Consumer habits that lower the cost

  • Never SELECT * in application code, since a new column changes the result shape.
  • Never rely on column order or JSON key order.
  • Be a tolerant reader: ignore unknown fields. Strict validation belongs on fields actually used, not on the whole payload.
  • Keep migrations in the repository with the code that needs them.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.