Миграции с Alembic
Миграции - то, что превращает «поменял модель» в воспроизводимое изменение боевой БД без потери данных и даунтайма. Собес проверяет, доверяешь ли ты autogenerate вслепую и знаешь ли, как навесить NOT NULL на большую таблицу без блокировки.
Типовые формулировки: «что делает autogenerate и можно ли ему верить?», «как добавить NOT NULL колонку без даунтайма?», «что такое down_revision».
Ревизии, autogenerate и его слепые зоны
Alembic ведёт версионируемую историю схемы: каждая ревизия это upgrade и downgrade, применяемые воспроизводимо. Это заменяет ручные ALTER «по памяти», которые невозможно откатить и повторить. down_revision - ссылка на родительскую ревизию; по этим ссылкам Alembic строит цепочку и знает порядок наката и отката.
autogenerate набрасывает миграцию по разнице моделей и схемы, но видит НЕ всё: пропускает часть изменений типов и, что опаснее, путает переименование колонки с drop+add. Применить такое вслепую - потерять данные переименованной колонки. Поэтому autogenerate всегда ревьюят руками.
// Две ветки с одним down_revision дают конфликт множественных голов - его разрешают слиянием ревизий (alembic merge).
- ревизия
- шаг миграции: upgrade + downgrade
- down_revision
- ссылка на предыдущую ревизию в цепочке
Zero-downtime: NOT NULL в три шага
Одношаговый ADD COLUMN ... NOT NULL на большой таблице берёт долгую блокировку - сервис встаёт на время. Zero-downtime делают в три шага: сначала добавить колонку как NULLABLE (быстро), затем бэкфилл значений БАТЧАМИ (не одним UPDATE на всю таблицу), потом навесить ограничение NOT NULL, когда пустых значений уже нет.
Тот же принцип у любой тяжёлой миграции: разбивай на шаги, каждый из которых берёт короткую блокировку или не берёт её вовсе, и держи совместимость со старым кодом на время выката.
// autogenerate этих тонкостей не знает - стратегию zero-downtime пишут руками поверх его черновика.
- autogenerate
- черновик миграции по разнице моделей и схемы
- zero-downtime
- миграция без остановки сервиса, в несколько шагов
Как отвечать: «Как добавить NOT NULL колонку в большую таблицу без даунтайма?»
Не одним ALTER - ADD COLUMN NOT NULL на большой таблице берёт долгую блокировку и роняет сервис. Делаю в три шага. Первый: добавляю колонку как nullable, это быстрая операция. Второй: бэкфиллю значения батчами, скажем по несколько тысяч строк за транзакцию, чтобы не держать одну гигантскую блокировку и не раздувать WAL. Третий: когда NULL-ов не осталось, навешиваю ограничение NOT NULL. На всё время код должен корректно работать и со старой, и с новой схемой. И миграцию от autogenerate я всегда просматриваю руками - он, например, переименование делает как drop и add с потерей данных.
Дан точный трёхшаговый рецепт с причиной каждого шага (блокировка, WAL), учтена совместимость кода и добавлена бдительность к autogenerate - ответ человека, делавшего это на проде.
На чём валят
- −Применять autogenerate вслепую: переименование колонки он сделает как drop+add с потерей данных.
- −Одной миграцией вешать NOT NULL на большую таблицу - долгая блокировка и риск даунтайма.
- −Две ветки с одним down_revision дают конфликт голов - нужно слияние ревизий.
- −Бэкфилл одним UPDATE на всю таблицу вместо батчей - долгая блокировка и раздувание WAL (write-ahead log).
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 11, остальные разбираются в тренажёре.
- Почему autogenerate в Alembic нельзя применять вслепую?A)Он требует ручного пересоздания базы перед каждой генерациейB)Он видит не все изменения и путает переименованияC)Он работает только с SQLite и ломается на PostgreSQL в продеD)Он удаляет все данные из изменяемых таблиц при накате
показать ответ и разбор
+B)Он видит не все изменения и путает переименования// разбор: autogenerate сравнивает модели с текущей схемой и набрасывает миграцию, но видит не всё: пропускает изменения типов в части случаев, не понимает переименование колонки (генерирует drop+add с потерей данных), не трогает данные. Поэтому автогенерацию всегда ревьюят и правят руками.
- Что задаёт
down_revisionв файле ревизии Alembic?A)Флаг, разрешающий понижать схему без потери данныхB)SQL для отката именно этой миграции при downgradeC)Родительскую ревизию — предыдущий шаг в цепочкеD)Номер версии приложения, к которой привязана миграцияпоказать ответ и разбор
+C)Родительскую ревизию — предыдущий шаг в цепочке// разбор: down_revision указывает на предыдущую ревизию — так Alembic выстраивает миграции в направленную цепочку (кто за кем). revision — id самой миграции, down_revision — её родитель. Именно по этим ссылкам Alembic знает порядок наката и куда откатываться; ветвление истории даёт конфликты слияния ревизий.
- Добавляем NOT NULL колонку в большую таблицу без даунтайма. Как правильно?A)Отключить проверки внешних ключей на время наката миграцииB)Сначала nullable, бэкфилл данных, потом ограничениеC)Удалить таблицу и пересоздать её уже с нужной колонкойD)Одной миграцией: добавить колонку сразу с NOT NULL и дефолтом
показать ответ и разбор
+B)Сначала nullable, бэкфилл данных, потом ограничение// разбор: Zero-downtime приём: (1) добавить колонку nullable (быстро, старый код живёт), (2) бэкфилл значений батчами, (3) когда всё заполнено и новый код пишет колонку — навесить NOT NULL. Одношаговый ADD NOT NULL на большой таблице берёт долгую блокировку и переписывает таблицу.
- Зачем нужны миграции схемы (Alembic)?A)Чтобы ускорять запросы, периодически перестраивая индексы всех таблиц базыB)Чтобы регулярно переносить данные между разными физическими серверами баз данныхC)Версионировать изменения структуры БД и воспроизводимо накатывать их во всех средахD)Чтобы автоматически создавать резервные копии таблиц перед каждым запросом к ним
показать ответ и разбор
+C)Версионировать изменения структуры БД и воспроизводимо накатывать их во всех средах// разбор: Миграции описывают изменения структуры БД (таблицы, колонки, индексы, ограничения) версионированными шагами в коде. Их накатывают одинаково на dev/CI/prod, откатывают, хранят в git — схема эволюционирует воспроизводимо и синхронно с кодом. Alembic для этого связан с моделями SQLAlchemy. Без миграций структура правится вручную и расходится между средами.
- Alembic умеет автогенерировать миграции по моделям. В чём подвох автогенерации?A)Она генерирует лишь откат (downgrade), а накат (upgrade) пишут рукамиB)Она работает только при пустой базе данных и бесполезна для существующих схемC)Ни в чём: автогенерация даёт полностью корректную миграцию без правокD)Она ловит не всё (переименования, часть ограничений) — ревизию надо вычитывать
показать ответ и разбор
+D)Она ловит не всё (переименования, часть ограничений) — ревизию надо вычитывать// разбор: autogenerate сравнивает метаданные моделей с текущей схемой БД и предлагает миграцию — экономит время, но не всесилен: переименование колонки видит как drop+add (потеря данных), не всегда ловит изменения типов, серверные дефолты, часть ограничений и индексов. Поэтому сгенерированную ревизию обязательно читают и правят перед применением, а не катят вслепую.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.