«ALTER TABLE» в пик трафика — классический способ положить iGaming или маркет в субботу. Миграции проектируют как релизы: expand → deploy → contract.

Expand / contract
Добавить nullable column → deploy код, пишущий в оба → backfill → NOT NULL → удалить старое.
Индексы
- CREATE INDEX CONCURRENTLY — отдельная миграция
- Не блокировать таблицы orders/wallet на минуты
Sequelize / raw SQL
- up/down идемпотентны где возможно
- Тяжёлые миграции — maintenance window или feature flag read path

Чеклист
- Staging = copy prod schema
- Rollback plan documented
- Monitoring на lock wait
Пример expand/contract
Релиз 1: ADD COLUMN new_email nullable. Релиз 2: код пишет в email и new_email. Релиз 3: backfill job ночью. Релиз 4: NOT NULL + drop old column.
Так вы избегаете блокировки таблицы users на ALTER с NOT NULL в пик трафика.
Мониторинг миграций
- Алерт на lock wait > 30s на prod
- Лог длительности каждой migration up
- Запрет destructive DDL без ticket и окна
Staging как копия prod
Перед релизом прогоняйте миграции на anonymized dump с объёмом хотя бы 10% prod. На пустой БД миграция «летит», на миллионах строк — внезапный ACCESS EXCLUSIVE lock.
Down-миграции и rollback
Down не всегда симметричен up: после backfill и NOT NULL откат может быть невозможен без потери данных. Документируйте «rollback = restore snapshot + redeploy previous tag» для тяжёлых миграций.
Sequelize migration в транзакции: DDL в PostgreSQL частично auto-commit — тяжёлые шаги выносите в отдельные файлы и мониторьте pg_stat_activity во время apply.
Если миграция уже тормозит prod
- pg_stat_activity: какой lock и на какой таблице
- Отмените необязательный DDL, перенесите в окно
- CREATE INDEX CONCURRENTLY вместо обычного CREATE INDEX
- Разбейте одну миграцию на expand (nullable) и contract (NOT NULL) в разных релизах