Короткий ответ
Безопасный откат обеспечивается, когда миграция и код спроектированы так, что предыдущая версия кода продолжает корректно работать и после применения новой миграции (backward-compatible migration) — тогда откат кода не требует отдельного откат миграции «на лету»; миграции, которые физически удаляют данные/колонки или делают их несовместимыми для старого кода, нужно разносить на отдельные более поздние релизы.
Как это работает подробнее
При обнаружении проблемы после релиза самый быстрый способ восстановления — откатить деплой кода до предыдущей версии; если миграция уже физически изменила несовместимым образом структуру (удалила колонку), откат кода сам по себе не восстановит работоспособность без восстановления колонки и данных.
Сравнение
- Компромисс. Многоэтапный безопасный процесс требует больше времени и дисциплины релизного процесса, чем прямое изменение схемы одним релизом; команда должна явно решить, какой уровень риска приемлем — для некритичных внутренних сервисов можно допустить более быстрый, но менее безопасный путь, тогда как для платёжных/учётных систем обязательна многоэтапная стратегия с протестированным откатом.
Пример кода
Сценарий
План релиза с рискованной миграцией:
1) Снять логический снапшот/бэкап перед миграцией (point-in-time recovery доступен на N часов назад).
2) Выполнить только backward-compatible часть миграции (expand): добавить новую колонку nullable.
3) Деплой кода, который пишет в обе колонки.
4) Наблюдение (canary/мониторинг ошибок) в течение оговорённого окна.
5) Contract-миграция (удаление старой колонки) — отдельным релизом, только после подтверждения
стабильности и явного решения, что откат к состоянию "до шага 2" больше не рассматривается.Что использовать на практике
- Практика. Стратегия — разделять рискованные структурные изменения на несколько релизов (expand → перенести код на новую структуру → contract), держать миграции обратимыми, тестировать сценарий rollback на стейджинге; для необратимых операций заранее делать резервную копию или снапшот.
Подводные камни
- Ошибка. Пишут функцию downgrade() формально, но никогда не тестируют её — на реальном откате выясняется, что она не работает или теряет данные; считают, что блокировка на пару секунд при ALTER TABLE — это нормально, без проверки на реальном объёме production-таблицы.