Executando verificação de segurança...
2

Database Migrations Seguras em Produção com Prisma

Uma coluna NOT NULL adicionada sem valor default em uma tabela com 12 milhões de linhas trava o banco por minutos. O Prisma gera a migration correta do ponto de vista de schema, mas não tem como saber que aquela ALTER TABLE vai adquirir um ACCESS EXCLUSIVE lock no Postgres e bloquear todas as queries enquanto reescreve a tabela inteira.

Esse é o tipo de problema que este post resolve: a distância entre o que o Prisma gera automaticamente e o que produção exige de verdade.

O modelo mental: migrations são deploys de infraestrutura

Tratar migration como "arquivo SQL que roda antes do app subir" é o primeiro erro. Migrations alteram a estrutura de um sistema stateful. Diferente de código aplicação (que você faz rollback com um revert de commit), uma migration que dropa uma coluna destrói dados de forma irreversível.

A regra operacional é: toda migration em produção deve ser compatível com a versão anterior E a versão seguinte do código da aplicação. Isso tem nome: expand-and-contract.

FaseO que acontece no bancoO que acontece no código
ExpandAdiciona coluna/tabela nova, sem remover nadaCódigo novo escreve nos dois lugares (antigo e novo)
MigrateBackfill de dados, criação de índicesCódigo novo lê do lugar novo, escreve nos dois
ContractRemove coluna/tabela antigaCódigo antigo já não existe em produção

Cada fase é um deploy separado. Tentar fazer as três em uma única migration é o caminho para downtime.

Configuração base do Prisma para ambientes reais

Antes de falar de migrations, o schema.prisma precisa estar configurado para separar ambientes corretamente:

// schema.prisma
// O shadowDatabaseUrl é obrigatório para prisma migrate dev em ambientes
// onde o usuário do banco não tem permissão CREATE DATABASE (quase todo ambiente gerenciado)
datasource db {
  provider          = "postgresql"
  url               = env("DATABASE_URL")
  shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
}

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["driverAdapters"]
  binaryTargets   = ["native", "linux-musl-openssl-3.0.x"]
}

A variável SHADOW_DATABASE_URL aponta para um banco descartável que o Prisma usa para calcular diffs. Em produção, o comando prisma migrate deploy nunca usa shadow database: ele aplica as migrations pendentes sequencialmente. A confusão entre migrate dev (desenvolvimento) e migrate deploy (produção) causa metade dos problemas que vejo em projetos.

# Desenvolvimento: gera migration a partir do diff entre schema.prisma e shadow database
npx prisma migrate dev --name add_status_to_orders

# Produção: aplica migrations pendentes na ordem, sem gerar nada novo
npx prisma migrate deploy

Expand-and-contract na prática com Prisma

Cenário concreto: renomear a coluna userName para displayName na tabela User.

Fase 1: Expand (adicionar coluna nova)

// schema.prisma - fase expand
model User {
  id          String  @id @default(cuid())
  email       String  @unique
  userName    String  // mantém a coluna antiga
  displayName String? // nova coluna, nullable para não quebrar inserts existentes
  createdAt   DateTime @default(now())
}
npx prisma migrate dev --name add_display_name_to_user

O código da aplicação passa a escrever nas duas colunas:

// user-service.ts - fase expand
// Escrita dual garante que tanto código antigo quanto novo encontram dados válidos
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

async function updateUserName(userId: string, newName: string) {
  return prisma.user.update({
    where: { id: userId },
    data: {
      userName: newName,
      displayName: newName, // escrita dual: popula a coluna nova em paralelo
    },
  });
}

Fase 2: Migrate (backfill)

Crie uma migration SQL customizada para preencher os registro


Leia o artigo completo em https://www.vivodecodigo.com.br/backend/database-migrations-seguras-producao-prisma

Carregando publicação patrocinada...