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:
- "When removing a column, application code must be deployed before the schema is changed"
- "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:
- Deploy application code that stops referencing the column (both reads and writes).
- Verify nothing writes to the column anymore.
- 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:
- Deploy the schema change that adds the column (nullable, or with a default).
- 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 removeignored_columnsand deploy. Rename-column = full five-deploy parallel-change sequence (add new → dual- write via ActiveRecord callbacks →find_eachbackfill → 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 viasys.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).
Related¶
- concepts/coupled-vs-decoupled-database-schema-app-deploy
- concepts/backward-compatibility
- patterns/expand-migrate-contract
- patterns/pr-bot-auto-deploy-request
- systems/planetscale
-
— FK-topology generalisation altitude. Noach + Gupta (2023-12-05) generalise the concept from "which of code-vs-schema ships first" to "which of multiple schema-migration operations must run in what order, and which can run concurrently". Canonical FK-specific examples: (a) create parent table before child table; (b) index parent's referenced column before adding the child's FK; (c) migrate
t1fully before migratingt2whent2's new FK references a new column ont1; (d) migratet2andt3concurrently when both newly reference an existing column ont1that doesn't change (even thought2andt3share a common parentt1, the migration's modifying onlyt2/t3). The computation is performed byschemadiffat deploy-request submission time.