Урок по треку Python для веба: Django и FastAPI 7 мин

Миграции в Django и Alembic: как менять схему без простоя

Миграции Django и Alembic, данные в миграциях, конфликты и изменения схемы без простоя.

Миграция кажется рутиной, пока она не заблокирует таблицу заказов на 10 минут в час пик. Middle должен уверенно работать с миграциями, senior должен уметь менять схему большой базы без простоя.

Миграции Django

python manage.py makemigrations   # сравнить модели с состоянием миграций и создать файл
python manage.py migrate          # применить к базе
python manage.py showmigrations   # что применено
python manage.py sqlmigrate app 0007   # какой SQL выполнится
  • Файл миграции содержит dependencies (от каких миграций зависит) и operations (AddField, AlterField, RunPython...).
  • Применённые миграции записываются в таблицу django_migrations.
  • Миграции коммитят в репозиторий вместе с кодом и не редактируют задним числом, если они уже применены где-то кроме вашей машины.

Alembic (SQLAlchemy, FastAPI)

alembic revision --autogenerate -m "add status"
alembic upgrade head
alembic downgrade -1

Ревизии связаны через down_revision, текущая версия лежит в таблице alembic_version. Autogenerate сравнивает модели и схему, но не всё видит (переименования он видит как удаление плюс добавление), поэтому сгенерированный файл всегда читают глазами.

Миграции данных

Изменение данных (заполнить новое поле, перенести значения) делают отдельной миграцией:

def fill_slug(apps, schema_editor):
    Article = apps.get_model('blog', 'Article')   # историческая модель
    for a in Article.objects.filter(slug='').iterator():
        a.slug = slugify(a.title)
        a.save(update_fields=['slug'])

class Migration(migrations.Migration):
    dependencies = [('blog', '0012_article_slug')]
    operations = [migrations.RunPython(fill_slug, migrations.RunPython.noop)]

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

Конфликты

Два разработчика в разных ветках создали 0008_... в одном приложении. После слияния Django увидит два «листа» и попросит makemigrations --merge, который создаст миграцию-слияние. В Alembic аналогично: две ревизии с одним down_revision объединяют alembic merge.

Изменения без простоя (expand и contract)

При деплое старый и новый код работают одновременно (rolling update), поэтому схема должна подходить обоим.

  1. Expand: добавить новое (колонку nullable или с дефолтом, новую таблицу, индекс конкурентно).
  2. Выкатить код, который пишет в оба места или понимает оба варианта.
  3. Migrate: перенести данные пачками.
  4. Выкатить код, который использует только новое.
  5. Contract: удалить старое отдельным релизом.

Переименование колонки «в лоб» ломает старый код, который ещё работает. Удаление колонки, которую читает старый код, тоже.

Опасные операции в PostgreSQL

  • ADD COLUMN ... NOT NULL без дефолта на непустой таблице не применится, а с изменчивым дефолтом перепишет таблицу. С PostgreSQL 11 добавление колонки с константным дефолтом быстрое.
  • CREATE INDEX блокирует запись в таблицу: на больших таблицах CREATE INDEX CONCURRENTLY (в Django AddIndexConcurrently, миграция с atomic = False).
  • Смена типа колонки часто переписывает таблицу.
  • Любая DDL-операция берёт блокировку и может встать в очередь за долгой транзакцией, а за ней встанут все запросы. Ставят lock_timeout.

Частые ошибки

  • Импорт модели в миграцию данных вместо apps.get_model.
  • Миграция данных в той же транзакции, что и тяжёлая схема, на миллионах строк.
  • Редактирование уже применённой миграции.
  • Удаление колонки одновременно с выкладкой кода, который перестал её использовать.
  • Отсутствие обратной операции там, где откат реально нужен.

Что спросят на собесе

  • Как работают миграции в Django или Alembic, где хранится состояние.
  • Как написать миграцию данных и почему apps.get_model.
  • Что делать при конфликте миграций.
  • Как переименовать колонку или добавить NOT NULL поле без простоя.
  • Какие операции опасны на большой таблице PostgreSQL.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.