Главная/Блог/Миграции базы данных в NestJS и Prisma: как менять структуру без простоя и потери данных

Миграции базы данных в NestJS и Prisma: как менять структуру без простоя и потери данных

Миграции базы данных в NestJS и Prisma: как менять структуру без простоя и потери данных
Каждое развитие продукта — новый способ оплаты, программа лояльности, разделение таблицы — рано или поздно требует изменить структуру базы данных. В работающем приложении это самое рискованное место разработки: неудачная миграция может «уронить» сайт на часы или необратимо повредить данные клиентов. По разным оценкам, минута простоя интернет-магазина в час пик обходится в десятки тысяч рублей недополученной выручки. Хорошая новость: в связке NestJS + Prisma есть проверенный набор практик, который позволяет обновлять схему так, что пользователи ничего не замечают. Разберём их по шагам — от устройства миграций до плана отката.

Почему «ручные» правки базы — главный источник потерь

Типичная ситуация: разработчик открывает pgAdmin и добавляет колонку прямо в боевую базу «на пять минут». Через месяц никто не помнит, зачем она нужна, код перестаёт совпадать с реальной структурой, а новый релиз падает на чужом окружении. Это называется drift — расхождение схемы и кода, и именно оно становится причиной большинства аварийных простоев. Ручные правки не оставляют истории: невозможно узнать, кто, когда и зачем изменил таблицу, и невозможно откатить изменение. Миграции решают все четыре проблемы: каждое изменение — это версионированный файл в git, который применяется одинаково на всех окружениях.

Как устроены миграции в Prisma

Prisma хранит схему данных в файле schema.prisma — это единый источник правды для всей команды. Команда prisma migrate dev создаёт из diff'а схемы SQL-миграцию и сохраняет её в папку prisma/migrations. На production применяется только prisma migrate deploy: она накатывает неприменённые миграции строго в порядке нумерации и ничего не «придумывает» сама. Критично не путать команды — prisma db push и migrate dev на боевом сервере запрещены: первая меняет схему без истории и может молча удалить данные, вторая может запросить интерактивный сброс. В NestJS-проектах миграции выносят в отдельный шаг CI/CD-пайплайна, а не запускают при старте приложения, иначе несколько инстансов попытаются изменить схему одновременно.

Главный принцип: расширяй схему, не ломая старый код

Этот паттерн называют expand–migrate–contract: сначала расширяем схему, затем переносим данные и только в конце убираем устаревшее. Никогда не делайте разрушающие операции — переименование, смену типа, удаление — одним шагом: они требуют одновременного обновления всех копий приложения и остановки сервиса. Вместо этого разбейте изменение на серию обратно совместимых релизов:
  1. Добавьте новое поле или таблицу, не трогая старые (nullable или со значением по умолчанию).
  2. Выкатите код, который пишет в новую структуру, но продолжает читать старую.
  3. Заполните исторические данные скриптом-бэкфиллом.
  4. Переключите чтение на новую структуру и сделайте деплой.
  5. Удалите устаревшие колонки только через 1–2 релиза, убедившись, что старый код нигде не остался.
Каждый шаг можно откатить независимо, а приложение работает без остановки на всех пяти этапах.

Перенос данных: батчи вместо одного гигантского запроса

Классическая ошибка — заполнить новое поле для 10 миллионов строк одним UPDATE. Такой запрос держит блокировки, раздувает журналы транзакций и может «подвесить» продакшен на десятки минут. Правильный подход — фоновый бэкфилл-скрипт, который не мешает бизнесу:
  • Обрабатывайте данные батчами по 1 000–5 000 строк с паузой 50–100 мс между итерациями.
  • Делайте скрипт идемпотентным: если он упал на середине, повторный запуск продолжает с того же места, а не начинает заново.
  • Запускайте в часы минимальной нагрузки и логируйте прогресс: сколько строк обработано, сколько осталось.
  • Для тяжёлых индексов в PostgreSQL используйте CREATE INDEX CONCURRENTLY — он строит индекс, не блокируя запись.
Бэкфилл из 10 млн строк при таком подходе занимает часы, но пользователи не замечают ничего — кроме новой функции.

Порядок релиза: схема всегда раньше кода

Миграция должна применяться до старта новой версии приложения, а не после. Если код, который читает новое поле, окажется на серверах раньше самого поля, пользователи получат 500-е ошибки. В пайплайне шаг migrate deploy ставится перед деплоем контейнеров, и окружения проходятся по порядку: staging первым, production последним. Обязательно тестируйте миграцию на копии боевой базы: staging с базой на 1 000 строк не покажет, что UPDATE по 10 млн записей займёт 40 минут. Ещё одно правило: одна миграция — одна задача; не смешивайте в одном файле быстрое изменение схемы и долгую правку данных.

Бэкапы и план отката — обязательный этап

Перед любой миграцией на продакшене делается бэкап, и не формально: проверьте, что из него реально можно восстановиться, и засеките время восстановления. Для PostgreSQL идеален PITR (point-in-time recovery) — в облачных провайдерах он включается одной галочкой. Заранее пропишите план отката: при обратно совместимых миграциях откат чаще означает «вернуть предыдущую версию кода», а не «перевернуть схему» — это быстрее и безопаснее. Prisma не поддерживает down-миграции из коробки, поэтому обратное изменение схемы пишут отдельной миграцией — и тоже прогоняют на staging, а не импровизируют в момент аварии.

Пять ошибок, которые приводят к простою

Даже опытные команды регулярно наступают на одни и те же грабли. Проверьте, нет ли этих пунктов в вашем процессе:
  • prisma db push на продакшене — меняет схему без истории миграций и может молча удалить данные при расхождении.
  • Правка уже применённой миграции — контрольная сумма не сойдётся, пайплайн упадёт; любое исправление вносится новой миграцией.
  • Переименование колонки в один шаг — вместо этого делается связка «новая колонка + синхронизация данных + удаление старой».
  • ALTER TABLE на большой таблице без учёта блокировок — в PostgreSQL многие изменения берут эксклюзивную блокировку и встают в очередь за всеми запросами, останавливая запись.
  • Миграции при старте приложения — при нескольких инстансах они сработают одновременно; запускайте их отдельным шагом деплоя.

Что спросить у подрядчика про работу с базой

Даже если вы не погружаетесь в код, дисциплина миграций — точный индикатор зрелости команды. Спросите, как устроен процесс: где хранятся миграции, запускается ли migrate deploy в пайплайне, тестируется ли релиз на копии продакшена, есть ли бэкап и план отката перед каждым изменением схемы. Зрелая команда ответит конкретно и покажет историю миграций в репозитории; отговорки вида «мы правим базу напрямую, там всё просто» — повод насторожиться. Для бизнеса это не формальность: от этих процессов зависит, сколько минут в час пик ваш сайт будет недоступен и насколько безболезненно вы сможете развивать продукт.
Вывод: безопасные изменения схемы — это не удача, а процесс: версионированные миграции Prisma, обратная совместимость по схеме expand–migrate–contract, батч-бэкфилл, деплой схемы до кода и проверенный бэкап. В связке NestJS + Prisma всё это автоматизируется в CI/CD и не требует останавливать приложение. Если ваши обновления до сих пор сопровождаются ночными окнами и нервами дежурного разработчика — самое время навести порядок. Наша команда настраивает пайплайны миграций и проводит аудит текущих схем: напишите нам, чтобы обсудить ваш проект.

Нужна разработка?

Оставьте заявку — обсудим ваш проект и предложим решение