Expand and contract
The compatibility window is the product:
old app + old schema | v EXPAND <- safe while old writers and readers exist | vold app + expanded schema | v DEPLOY <- new app understands both schema contracts | vnew app + expanded schema | v DRAIN <- prove old traffic is gone | v CONTRACT <- validate, tighten, remove compatibilityExample: add a required column
Section titled “Example: add a required column”The end state may be simple:
CREATE TABLE app.bookings ( id bigint PRIMARY KEY, status text NOT NULL);Adding it as required in one operation can break old inserts. A compatible route is:
-- expand.sqlALTER TABLE app.bookings ADD COLUMN status text;
-- application-owned backfill goes here when requiredUPDATE app.bookings SET status = 'pending' WHERE status IS NULL;Deploy code that writes and reads status, drain old writers, then enforce the contract:
-- contract.sqlALTER TABLE app.bookings ADD CONSTRAINT bookings_status_present CHECK (status IS NOT NULL) NOT VALID;
ALTER TABLE app.bookings VALIDATE CONSTRAINT bookings_status_present;
ALTER TABLE app.bookings ALTER COLUMN status SET NOT NULL;ALTER TABLE app.bookings DROP CONSTRAINT bookings_status_present;The generated details depend on the catalog and your decisions; the important point is that application compatibility determines phase placement.
The one-deployment rule
Section titled “The one-deployment rule”The new application version must work immediately after expand and still work after contract. If removing the old shape requires another code release, keep the compatibility object and choose a split plan. The later feature owns its removal.
Backfills are not a third deployment phase. They are explicit work scheduled where old and new contracts remain safe. Contract should never be used as a vague container for “do risky things later.”