A schema change on a table people are writing to is not one change. It is a sequence, and the app has to work at every step - including the moment when half your servers are running the old code.
Expand
Add the new column as nullable. Deploy code that writes to both old and new but still reads from the old. Nothing depends on the new column yet, so nothing can break.
Migrate
Backfill in batches, with a limit and a sleep. A single UPDATE across ten million rows will lock the table and take the site with it.
Contract
Switch reads to the new column, deploy, watch. Only then stop writing to the old one, and only after that drop it - usually a week later, once you are sure you will not need to roll back.
The rules that keep it safe
- Every step must be independently deployable and reversible
- Never rename - add, copy, then remove
- Test the rollback, not just the migration
- Run it against a copy of production data first