Миграции
самое необратимое, что есть в проекте
Что это
Миграция — изменение структуры базы: добавить таблицу, колонку, индекс, переименовать поле. Это тот самый правый нижний угол из статьи 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 без умолчания: если в таблице уже есть строки,
миграция просто не выполнится. Это ещё хороший случай — хуже, когда она
выполняется и портит данные.
Обязательный порядок
- Резервная копия. До всего остального. Для SQLite — копия файла, для
PostgreSQL —
pg_dump. - Прогон на копии. Не на боевой базе. Миграция, которая отработала на пустой тестовой базе, ничего не доказывает — нужна копия с реальными данными.
- Проверка отката. Как вернуть, если не получилось. Если ответа нет, миграцию нельзя применять (статья 1.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не вернёт данные.Применённую миграцию не правят — пишут следующую.
Безопасно: добавить. Опасно: удалить, переименовать, сузить.
Переименование — три шага, а не один.
Порядок: копия → прогон на копии с данными → план отката → применение → проверка.
Структура и заполнение данных — разные файлы.