Проблема миграций в продакшене
Большинство руководств по миграциям показывают идеальный сценарий, но реальность сложнее:
- Живой трафик во время выполнения миграций.
- Старая версия приложения продолжает работать до завершения деплоя.
- Неудачные миграции требуют отката изменений.
- Длительные миграции блокируют таблицы базы данных.
Безопасный паттерн миграций
Самый безопасный подход — это расширение схемы, затем сокращение её после полной замены старого кода новым.
Шаг 1: Расширение (обратно совместимое изменение)
Добавляем новый столбец как допускающий NULL, чтобы не нарушать работу старой версии приложения:
ALTER TABLE users ADD COLUMN display_name TEXT;
Шаг 2: Заполнение данными (фоновый процесс)
Используем скрипт для постепенного заполнения нового поля значениями из существующего столбца:
const batchSize = 1000
let cursor: string | undefined
while (true) {
const users = await prisma.user.findMany({
take: batchSize,
skip: cursor ? 1 : 0,
cursor: cursor ? { id: cursor } : undefined,
where: { displayName: null },
})
if (users.length === 0) break
await prisma.user.updateMany({
where: { id: { in: users.map(u => u.id) } },
data: { displayName: users.map(u => u.name) },
})
cursor = users[users.length - 1].id
await sleep(100) // избегаем перегрузки БД
}
Шаг 3: Сокращение (после полного развёртывания новой версии)
Теперь можно сделать поле обязательным и удалить старый столбец позже:
ALTER TABLE users ALTER COLUMN display_name SET NOT NULL;
ALTER TABLE users DROP COLUMN name;
Рабочий поток миграций Prisma
Разработка
Создаём миграцию на основе изменений в схеме:
npx prisma migrate dev --name add_display_name
Просматриваем изменения перед запуском в продакшн:
npx prisma migrate status
Продакшн
Применяем ожидающие миграции:
npx prisma migrate deploy
Использование пользовательского SQL в миграциях
Можно создать файл миграции без запуска команд:
npx prisma migrate dev --name backfill_slugs --create-only
Затем редактируем созданный SQL-файл вручную:
-- migrations/20240101_backfill_slugs/migration.sql
ALTER TABLE posts ADD COLUMN slug TEXT;