Skip to content

CONCEPT

Schema-change deploy order

Definition

Schema-change deploy order is the operational rule governing which of the two deploy units (code or schema) must ship first for a given class of schema change so the production application never observes an inconsistent state. It is the expand-migrate- contract pattern reduced to a per-change-type instruction that a tool can emit to a developer at pull-request time.

The two canonical rules (Coutermarsh, 2024)

Mike Coutermarsh's 2024 PlanetScale post names them verbatim:

  1. "When removing a column, application code must be deployed before the schema is changed"
  2. "When adding a column, application code must be deployed after the schema is changed"

Both rules have the same structure: the system that is reducing scope deploys second; the system expanding scope deploys first.

Remove-column: code first

If the schema drops the column first, application code still writing/reading the column throws errors until the code deploy catches up. The safe order:

  1. Deploy application code that stops referencing the column (both reads and writes).
  2. Verify nothing writes to the column anymore.
  3. Deploy the schema change that drops the column.

Add-column: schema first

If code deploys first and tries to write to a column that doesn't exist yet, writes fail. The safe order:

  1. Deploy the schema change that adds the column (nullable, or with a default).
  2. Deploy application code that reads/writes the new column.

Why this is the operational instruction altitude of

expand-migrate-contract

The full expand-migrate- contract pattern has six steps (Expand → Write both → Backfill → Read new → Stop writing old → Contract). That's the full pattern for structural changes — renames, type changes, splits, merges. For the common additive / subtractive cases, the pattern collapses to a two-step sequence — which of the two deploy units goes first?

Coutermarsh's two rules encode the collapsed form. A PR-bot using the PlanetScale API can detect the class-of-change (add_column vs remove_column) and emit the appropriate rule as a comment on the PR, without asking the developer to internalise the full six-step pattern.

(Source: .)

The deeper principle

The rules derive from the two-critical-systems-cannot-deploy-atomically invariant. The code and the schema are two independent deploy units. During the window between deploys, the production application runs against a version-mismatched schema. The question is which direction of mismatch is safe:

Change First deploy Window state Safe?
Add column Schema Schema has new col, code doesn't read/write ✓ — code just ignores new col
Add column Code Code writes to col that doesn't exist ✗ — writes fail
Drop column Schema Col gone, code still reads/writes ✗ — reads/writes fail
Drop column Code Code stopped using col, col still exists ✓ — col is just unused

Rule-of-thumb: deploy the unit that expands the contract first, then deploy the unit that reduces the contract. Expansion is backward-compatible; reduction isn't.

Structural changes: neither order is safe → use full expand-migrate-contract

For renames, type changes, splits, and merges, neither two-step order is safe because the change is both an expansion and a reduction at once. The safe procedure requires the full six-step expand-migrate- contract dance: add the new representation alongside the old, dual-write, backfill, cut reads over, stop writing the old, drop the old.

The deploy-order rule for structural changes is therefore many deploys, not two: the sequence itself is the instruction.

Encoding as PR-bot output

The PR-bot pattern in patterns/pr-bot-auto-deploy-request emits the deploy-order rule as a comment on the pull request:

"With our bot, we are able to use the PlanetScale API to detect the class of changes being made to the database. The bot then generates comments based on the characteristics of the changes, including instructions for the sequence of steps needed to make the change safely for our application." (Source: )

This shifts the cognitive burden from "every engineer remembers the expand-contract pattern for every change type" to "the bot reads the diff and tells the engineer which of code-first / schema-first / full- expand-contract applies."

Seen in

  • Rails-workflow instantiation of the rule with five concrete change-class recipes walked end-to-end (Coutermarsh, 2023-03-20). Add-column = schema-first ("you must always deploy the schema change before any code using the column is deployed to production"). Drop-column = code-first via self.ignored_columns += %w(category) as the Rails bridge primitive ("This ensures your application is not using the column in production and removes the risk of any errors when you do run the migration"); then deploy request drops the column; then remove ignored_columns and deploy. Rename-column = full five-deploy parallel-change sequence (add new → dual- write via ActiveRecord callbacks → find_each backfill → switch reads + remove double-writes → drop old). Add-index = trivially schema-first ("there is no risk of table locking while adding or removing a database index"). Remove-index = schema-first but only after verifying unused via sys.schema_unused_indexes. Data migration = schema-change-first, data-reshape-via-Rails-console- second. Canonical Rails-idiomatic phrasing sibling to the Laravel-idiomatic Guevara version and the PR-bot-altitude Coutermarsh-2024 version.
  • — Laravel-workflow instantiation of the rule: Guevara answers "when do I run my migrations?" by schema-change shape with worked two-example framing. Verbatim Example 1: "You're adding a field to an input form in your application code, which also requires adding a column to one of your tables. In this case, you have to make sure your schema has been updated in production before that application code goes live." Verbatim Example 2: "You're getting rid of an existing column on one of your tables. In this case, you need to make sure you stop allowing writes to it from the application code before the schema goes live." Canonical Laravel-idiomatic phrasing of the schema-first-add / code-first-drop rule alongside the Rails-idiomatic Coutermarsh version.
  • — canonicalises the two-rule instruction format at PR-bot altitude.
  • — canonicalises the underlying expand-migrate-contract pattern (Barnett, 2024).
Last updated · 542 distilled / 1,571 read