Программирование с ИИ
ENRU
7.7

Миграции

самое необратимое, что есть в проекте

Что это

Миграция — изменение структуры базы: добавить таблицу, колонку, индекс, переименовать поле. Это тот самый правый нижний угол из статьи 1.5: необратимо и может сломаться незаметно.

Разница с обычным кодом принципиальная. Неверный код откатывается коммитом (статья 5.1); неверная миграция уже изменила данные, и git revert их не вернёт.

Как выглядит в коде

Миграции — пронумерованные файлы, которые применяются по порядку и только один раз:

измерено
migrations/
├── 001_initial.sql
├── 002_add_notify_hour.sql
└── 003_add_index_subs_user.sql
-- 002_add_notify_hour.sql
ALTER TABLE subs ADD COLUMN notify_hour INTEGER NOT NULL DEFAULT 9;

База хранит, какие миграции уже применены, и повторно их не запускает. Поэтому правило: применённую миграцию не правят. Ошибся — пиши следующую, которая исправляет.

Что безопасно, а что нет

БезопасноОпасно
добавить таблицуудалить таблицу
добавить колонку с DEFAULTудалить колонку
добавить индекспереименовать колонку
расширить тип (INT → BIGINT)сузить тип
добавить NOT NULL без DEFAULT

Левая колонка не ломает работающий код: старые запросы продолжают работать. Правая ломает — и ломает в момент применения, когда откатывать уже нечего.

Отдельно про NOT NULL без умолчания: если в таблице уже есть строки, миграция просто не выполнится. Это ещё хороший случай — хуже, когда она выполняется и портит данные.

Обязательный порядок

  1. Резервная копия. До всего остального. Для SQLite — копия файла, для PostgreSQL — pg_dump.
  2. Прогон на копии. Не на боевой базе. Миграция, которая отработала на пустой тестовой базе, ничего не доказывает — нужна копия с реальными данными.
  3. Проверка отката. Как вернуть, если не получилось. Если ответа нет, миграцию нельзя применять (статья 1.5).
  4. Применение.
  5. Проверка данных. Количество строк до и после, выборочные записи.

Пункт 2 — тот, который пропускают. А именно он ловит случаи вроде «в старых записях город пустой, и NOT NULL не применился».

Заполнение данных — отдельно

Миграция структуры и миграция данных — разные вещи:

-- 002: структура
ALTER TABLE subs ADD COLUMN notify_hour INTEGER NOT NULL DEFAULT 9;
# отдельный скрипт: заполнить осмысленными значениями
UPDATE subs SET notify_hour = 8 WHERE city IN (SELECT ...);

Разделять стоит потому, что скрипт заполнения можно запустить повторно, проверить на части данных и остановить. Миграцию — нет.

Как сказать ИИ

Запрос
Нужна миграция: добавить в subs колонку notify_hour,
целое от 0 до 23, по умолчанию 9. Существующие строки
не должны сломаться.

Отдельно скажи: что будет с кодом, который сейчас читает
эту таблицу, и нужно ли его менять до или после миграции.

Второй абзац ловит главную проблему миграций — порядок выкатки кода и схемы.

Запрос
Эта миграция переименовывает колонку одним ALTER.
Распиши её как три шага, чтобы код продолжал работать
между ними.
Запрос
Напиши обратную миграцию для этой. Если откатить нельзя
без потери данных — так и скажи, не придумывай.

На что смотреть

Нет обратной миграции. Не всегда возможна, но её отсутствие должно быть осознанным, а не случайным.

DROP в миграции. Удаление колонки или таблицы — всегда отдельное решение, принимаемое человеком, и обычно не раньше, чем через несколько релизов после того, как её перестали использовать.

Миграция и код выкатываются одновременно. Между применением миграции и обновлением кода проходит время, и в этот момент должно работать и то и другое.

Миграция правится после применения. На твоей машине она применится заново, на сервере — нет. Базы разъедутся.

Миграция содержит UPDATE на всю таблицу. Заполнение данных — отдельным скриптом.

Шпаргалка

  • Миграция необратима: git revert не вернёт данные.

  • Применённую миграцию не правят — пишут следующую.

  • Безопасно: добавить. Опасно: удалить, переименовать, сузить.

  • Переименование — три шага, а не один.

  • Порядок: копия → прогон на копии с данными → план отката → применение → проверка.

  • Структура и заполнение данных — разные файлы.