Как происходит доставка миграций на разные окружения, включая продакшн
Что проверяют
Понимаете ли вы, что миграция схемы это часть релиза с собственными рисками, а не SQL-файл, который кто-то запускает руками. Следом почти всегда спрашивают, как выкатить изменение схемы без простоя и что делать, если миграция упала на середине.
Как отвечать
Начните с принципа: миграции лежат в репозитории рядом с кодом, пронумерованы и проходят одинаковый путь через dev, стейджинг и прод. Никаких ручных правок базы на отдельном окружении, иначе схемы разъедутся.
- инструмент: в Go обычно
golang-migrate/migrateилиpressly/goose, реже Atlas с декларативной схемой. Версия схемы хранится в служебной таблице, применяются только новые файлы; - упаковка: файлы встраиваются в бинарник через
//go:embed, и нужная версия миграций едет вместе с образом сервиса; - когда применять: отдельным шагом конвейера до выкладки кода. В Kubernetes это Job, init-контейнер или Helm-хук
pre-upgrade. Запуск изmain()при старте каждой реплики опасен: несколько подов конкурируют за одну миграцию; - совместимость: изменения по шаблону expand/contract. Сначала добавляете новую колонку, код пишет в обе, данные переносятся пачками, и только в следующем релизе старая колонка удаляется;
- Postgres:
SET lock_timeoutпередALTER TABLE, чтобы миграция не встала в очередь блокировок и не заперла весь трафик. Индексы строятся черезCREATE INDEX CONCURRENTLY, который нельзя выполнять в транзакции, в goose для этого есть аннотацияNO TRANSACTION.
Для прода добавьте резервную копию или точку восстановления, а для больших таблиц прогон миграций на копии боевых данных, чтобы заранее узнать время и блокировки.
Где спотыкаются
Надеются на down-миграции как на откат. Удалённую колонку с данными down не вернёт, поэтому на проде надёжнее исправлять ситуацию новой миграцией вперёд.
Не знают про «грязное» состояние: golang-migrate при ошибке помечает версию как dirty и дальше не пойдёт, пока вы не разберётесь вручную и не выполните migrate force.
Пишут миграцию, несовместимую с текущим кодом. При rolling update старые реплики ещё работают и начинают падать на переименованной колонке.