сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Базы данных в Python

Миграции с Alembic

Миграции схемы через 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, остальные разбираются в тренажёре.

  1. #migrations_alembic1 / 5
    Почему autogenerate в Alembic нельзя применять вслепую?
    A)Он требует ручного пересоздания базы перед каждой генерацией
    B)Он видит не все изменения и путает переименования
    C)Он работает только с SQLite и ломается на PostgreSQL в проде
    D)Он удаляет все данные из изменяемых таблиц при накате
    показать ответ и разбор
    +B)Он видит не все изменения и путает переименования

    // разбор: autogenerate сравнивает модели с текущей схемой и набрасывает миграцию, но видит не всё: пропускает изменения типов в части случаев, не понимает переименование колонки (генерирует drop+add с потерей данных), не трогает данные. Поэтому автогенерацию всегда ревьюят и правят руками.

  2. #migrations_alembic2 / 5
    Что задаёт down_revision в файле ревизии Alembic?
    A)Флаг, разрешающий понижать схему без потери данных
    B)SQL для отката именно этой миграции при downgrade
    C)Родительскую ревизию — предыдущий шаг в цепочке
    D)Номер версии приложения, к которой привязана миграция
    показать ответ и разбор
    +C)Родительскую ревизию — предыдущий шаг в цепочке

    // разбор: down_revision указывает на предыдущую ревизию — так Alembic выстраивает миграции в направленную цепочку (кто за кем). revision — id самой миграции, down_revision — её родитель. Именно по этим ссылкам Alembic знает порядок наката и куда откатываться; ветвление истории даёт конфликты слияния ревизий.

  3. #migrations_alembic3 / 5
    Добавляем NOT NULL колонку в большую таблицу без даунтайма. Как правильно?
    A)Отключить проверки внешних ключей на время наката миграции
    B)Сначала nullable, бэкфилл данных, потом ограничение
    C)Удалить таблицу и пересоздать её уже с нужной колонкой
    D)Одной миграцией: добавить колонку сразу с NOT NULL и дефолтом
    показать ответ и разбор
    +B)Сначала nullable, бэкфилл данных, потом ограничение

    // разбор: Zero-downtime приём: (1) добавить колонку nullable (быстро, старый код живёт), (2) бэкфилл значений батчами, (3) когда всё заполнено и новый код пишет колонку — навесить NOT NULL. Одношаговый ADD NOT NULL на большой таблице берёт долгую блокировку и переписывает таблицу.

  4. #migrations_alembic4 / 5
    Зачем нужны миграции схемы (Alembic)?
    A)Чтобы ускорять запросы, периодически перестраивая индексы всех таблиц базы
    B)Чтобы регулярно переносить данные между разными физическими серверами баз данных
    C)Версионировать изменения структуры БД и воспроизводимо накатывать их во всех средах
    D)Чтобы автоматически создавать резервные копии таблиц перед каждым запросом к ним
    показать ответ и разбор
    +C)Версионировать изменения структуры БД и воспроизводимо накатывать их во всех средах

    // разбор: Миграции описывают изменения структуры БД (таблицы, колонки, индексы, ограничения) версионированными шагами в коде. Их накатывают одинаково на dev/CI/prod, откатывают, хранят в git — схема эволюционирует воспроизводимо и синхронно с кодом. Alembic для этого связан с моделями SQLAlchemy. Без миграций структура правится вручную и расходится между средами.

  5. #migrations_alembic5 / 5
    Alembic умеет автогенерировать миграции по моделям. В чём подвох автогенерации?
    A)Она генерирует лишь откат (downgrade), а накат (upgrade) пишут руками
    B)Она работает только при пустой базе данных и бесполезна для существующих схем
    C)Ни в чём: автогенерация даёт полностью корректную миграцию без правок
    D)Она ловит не всё (переименования, часть ограничений) — ревизию надо вычитывать
    показать ответ и разбор
    +D)Она ловит не всё (переименования, часть ограничений) — ревизию надо вычитывать

    // разбор: autogenerate сравнивает метаданные моделей с текущей схемой БД и предлагает миграцию — экономит время, но не всесилен: переименование колонки видит как drop+add (потеря данных), не всегда ловит изменения типов, серверные дефолты, часть ограничений и индексов. Поэтому сгенерированную ревизию обязательно читают и правят перед применением, а не катят вслепую.

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.