Files
spring-boot-demo/db-migrations-expand-contract/docs/09-the-contract-migration.md
T
asmhatreandClaude Sonnet 5 e478eafda3 Add db-migrations-expand-contract: zero-downtime schema migrations proven with a real 4-deploy rolling run
Companion code for Zero-Downtime Database Migrations: Expand-Contract in Practice
with Spring Boot: a full expand/migrate-writes/migrate-reads/contract sequence run
as an actual rolling deploy across two live replicas, with a load generator sending
continuous HTTP traffic through all four deploys (99.98% success, every residual
error traced to a root cause rather than left unexplained). Findings include a real
NOT NULL constraint trap in the expand migration, a backfill-window bug in the read
switch, H2's AUTO_SERVER=TRUE single-point-of-failure behavior under a rolling
restart, the drain-before-SIGTERM fix needed to close a health-check gap during
graceful shutdown, and H2 silently discarding a concurrently committed INSERT during
an ALTER TABLE ADD/DROP COLUMN rebuild - confirmed, by primary source, to be an
H2-specific behavior rather than a property of the technique itself.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019Fb7vW8vLyLKngBc4R3huA
2026-09-16 19:22:48 +00:00

2.5 KiB

9. The contract migration

← 8. The rolling window proof · Next: 10. What happens if you drop too soon →

Deploy 4 is split into two parts on purpose, the same way Deploy 1 was schema-only and Deploys 2/3 were code-only:

  • 4a — code. A rolling restart to Stage 4, which never reads or writes email. The old column is still physically present; Stage 4 code simply ignores it.
  • 4b — schema. Once every replica is confirmed on Stage 4 code, and only then, V3__drop_email_column.sql runs, with zero app restarts:
ALTER TABLE customers DROP COLUMN email;

ContractSafetyTest confirms the happy path: Stage 4 reads and writes keep working, both for a row that existed before the drop and for one created entirely after it —

-- Stage 4 read, after V3 dropped the email column --
Customer[id=1, name=Annie Easley, [email protected]]

-- Stage 4 create + read, entirely after the drop --
Customer[id=2, name=Mary Allen Wilkes, [email protected]]

Full transcript: docs/output/09-contract-safety.txt.

Why not drop the column in the same deploy as the code change? Because "every replica is confirmed on Stage 4" is a statement about the fleet, and a rolling deploy makes it true only once, at the very end of the rollout — never at the moment the deploy starts. Running 4b before that point is dropping a column a live Stage 1, 2, or 3 replica might still need. What that actually looks like, captured directly, is chapter 10.

Going deeper

  • The DDL Deploy 4b runs is not, on this specific database engine, quite as free of side effects on concurrent traffic as "just a schema change" suggests — see chapter 14 for what H2 actually does while this statement executes.

← 8. The rolling window proof · Next: 10. What happens if you drop too soon →