Как тестировать миграции базы данных?
Что проверяют
Относится ли кандидат к миграции как к продакшн-коду, который проверяется до релиза, и понимает ли риск блокировок на живой базе с данными.
Как отвечать
Что именно проверяется:
- Прогон на настоящей СУБД, а не на SQLite. PostgreSQL поднимается в контейнере (testcontainers или сервис в CI), все миграции применяются с нуля. Схема, собранная миграциями, должна совпадать со схемой, которую ожидают модели.
- Откат.
upgrade head, затемdowngrade, затем сноваupgrade. Еслиdowngradeне написан или падает, об этом узнают в момент инцидента, когда откатываться уже нужно срочно. - Согласованность моделей и схемы. Django:
manage.py makemigrations --check --dry-runпадает, если модели ушли вперёд файлов миграций. Alembic:alembic check, который требует пустого автогенерируемого диффа. - Миграции данных. Backfill проверяется на подготовленных краевых строках:
NULL, дубликаты, значения вне диапазона, битые кодировки. Отдельный тест на идемпотентность, если скрипт может запуститься дважды. - Совместимость версий. Релиз идёт по схеме expand и contract: сначала миграция, совместимая со старым кодом, потом новый код, и только следующим релизом удаление старой колонки. Тест: старая версия приложения на новой схеме.
def test_upgrade_downgrade(alembic_cfg):
command.upgrade(alembic_cfg, "head")
command.downgrade(alembic_cfg, "base")
command.upgrade(alembic_cfg, "head")Отдельная тема, которая отличает сильный ответ: время и блокировки на объёме, близком к продовому. В PostgreSQL начиная с 11 версии добавление колонки с константным DEFAULT не переписывает таблицу, а вот SET NOT NULL берёт ACCESS EXCLUSIVE и сканирует таблицу целиком (обойти можно заранее добавленным проверенным CHECK). Индекс на большой таблице создаётся через CREATE INDEX CONCURRENTLY, иначе записи встают на всё время построения. В миграции полезно выставлять lock_timeout и statement_timeout, чтобы деплой упал быстро, а не держал таблицу под замком.
Где спотыкаются
Тестируют миграции на пустой базе, где всё применяется мгновенно, и на проде впервые встречают и долгий backfill, и блокировку от ALTER TABLE. Второй типичный провал: код отката существует, но не запускался ни разу.