Migração de Banco de Dados
Domine migrações de schema e dados em ORMs (Sequelize, TypeORM, Prisma), incluindo estratégias de rollback e deployments sem downtime.
Não use esta skill quando
- A tarefa não está relacionada a migração de banco de dados
- Você precisa de um domínio ou ferramenta diferente fora deste escopo
Instruções
- Esclareça objetivos, restrições e entradas necessárias.
- Aplique as melhores práticas relevantes e valide resultados.
- Forneça passos práticos e verificação.
- Se exemplos detalhados forem necessários, abra
resources/implementation-playbook.md.
Use esta skill quando
- Migrar entre diferentes ORMs
- Realizar transformações de schema
- Mover dados entre bancos de dados
- Implementar procedimentos de rollback
- Deployments sem downtime
- Atualizações de versão de banco de dados
- Refatoração de modelo de dados
Migrações em ORMs
Migrações Sequelize
// migrations/20231201-create-users.js
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
email: {
type: Sequelize.STRING,
unique: true,
allowNull: false
},
createdAt: Sequelize.DATE,
updatedAt: Sequelize.DATE
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('users');
}
};
// Executar: npx sequelize-cli db:migrate
// Reverter: npx sequelize-cli db:migrate:undo
Migrações TypeORM
// migrations/1701234567-CreateUsers.ts
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
export class CreateUsers1701234567 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'users',
columns: [
{
name: 'id',
type: 'int',
isPrimary: true,
isGenerated: true,
generationStrategy: 'increment'
},
{
name: 'email',
type: 'varchar',
isUnique: true
},
{
name: 'created_at',
type: 'timestamp',
default: 'CURRENT_TIMESTAMP'
}
]
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('users');
}
}
// Executar: npm run typeorm migration:run
// Reverter: npm run typeorm migration:revert
Migrações Prisma
// schema.prisma
model User {
id Int @id @default(autoincrement())
email String @unique
createdAt DateTime @default(now())
}
// Gerar migração: npx prisma migrate dev --name create_users
// Aplicar: npx prisma migrate deploy
Transformações de Schema
Adicionando Colunas com Padrões
// Migração segura: adicionar coluna com padrão
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('users', 'status', {
type: Sequelize.STRING,
defaultValue: 'active',
allowNull: false
});
},
down: async (queryInterface) => {
await queryInterface.removeColumn('users', 'status');
}
};
Renomeando Colunas (Zero Downtime)
// Passo 1: Adicionar nova coluna
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('users', 'full_name', {
type: Sequelize.STRING
});
// Copiar dados da coluna antiga
await queryInterface.sequelize.query(
'UPDATE users SET full_name = name'
);
},
down: async (queryInterface) => {
await queryInterface.removeColumn('users', 'full_name');
}
};
// Passo 2: Atualizar aplicação para usar nova coluna
// Passo 3: Remover coluna antiga
module.exports = {
up: async (queryInterface) => {
await queryInterface.removeColumn('users', 'name');
},
down: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('users', 'name', {
type: Sequelize.STRING
});
}
};
Alterando Tipos de Coluna
module.exports = {
up: async (queryInterface, Sequelize) => {
// Para tabelas grandes, use abordagem multi-etapa
// 1. Adicionar nova coluna
await queryInterface.addColumn('users', 'age_new', {
type: Sequelize.INTEGER
});
// 2. Copiar e transformar dados
await queryInterface.sequelize.query(`
UPDATE users
SET age_new = CAST(age AS INTEGER)
WHERE age IS NOT NULL
`);
// 3. Remover coluna antiga
await queryInterface.removeColumn('users', 'age');
// 4. Renomear nova coluna
await queryInterface.renameColumn('users', 'age_new', 'age');
},
down: async (queryInterface, Sequelize) => {
await queryInterface.changeColumn('users', 'age', {
type: Sequelize.STRING
});
}
};
Transformações de Dados
Migração Complexa de Dados
module.exports = {
up: async (queryInterface, Sequelize) => {
// Obter todos os registros
const [users] = await queryInterface.sequelize.query(
'SELECT id, address_string FROM users'
);
// Transformar cada registro
for (const user of users) {
const addressParts = user.address_string.split(',');
await queryInterface.sequelize.query(
`UPDATE users
SET street = :street,
city = :city,
state = :state
WHERE id = :id`,
{
replacements: {
id: user.id,
street: addressParts[0]?.trim(),
city: addressParts[1]?.trim(),
state: addressParts[2]?.trim()
}
}
);
}
// Remover coluna antiga
await queryInterface.removeColumn('users', 'address_string');
},
down: async (queryInterface, Sequelize) => {
// Reconstruir coluna original
await queryInterface.addColumn('users', 'address_string', {
type: Sequelize.STRING
});
await queryInterface.sequelize.query(`
UPDATE users
SET address_string = CONCAT(street, ', ', city, ', ', state)
`);
await queryInterface.removeColumn('users', 'street');
await queryInterface.removeColumn('users', 'city');
await queryInterface.removeColumn('users', 'state');
}
};
Estratégias de Rollback
Migrações Baseadas em Transação
module.exports = {
up: async (queryInterface, Sequelize) => {
const transaction = await queryInterface.sequelize.transaction();
try {
await queryInterface.addColumn(
'users',
'verified',
{ type: Sequelize.BOOLEAN, defaultValue: false },
{ transaction }
);
await queryInterface.sequelize.query(
'UPDATE users SET verified = true WHERE email_verified_at IS NOT NULL',
{ transaction }
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
},
down: async (queryInterface) => {
await queryInterface.removeColumn('users', 'verified');
}
};
Rollback Baseado em Checkpoint
module.exports = {
up: async (queryInterface, Sequelize) => {
// Criar tabela de backup
await queryInterface.sequelize.query(
'CREATE TABLE users_backup AS SELECT * FROM users'
);
try {
// Executar migração
await queryInterface.addColumn('users', 'new_field', {
type: Sequelize.STRING
});
// Verificar migração
const [result] = await queryInterface.sequelize.query(
"SELECT COUNT(*) as count FROM users WHERE new_field IS NULL"
);
if (result[0].count > 0) {
throw new Error('Verificação de migração falhou');
}
// Descartar backup
await queryInterface.dropTable('users_backup');
} catch (error) {
// Restaurar do backup
await queryInterface.sequelize.query('DROP TABLE users');
await queryInterface.sequelize.query(
'CREATE TABLE users AS SELECT * FROM users_backup'
);
await queryInterface.dropTable('users_backup');
throw error;
}
}
};
Migrações sem Downtime
Estratégia Blue-Green Deployment
// Fase 1: Fazer mudanças compatíveis com versões anteriores
module.exports = {
up: async (queryInterface, Sequelize) => {
// Adicionar nova coluna (ambos os códigos antigos e novos podem funcionar)
await queryInterface.addColumn('users', 'email_new', {
type: Sequelize.STRING
});
}
};
// Fase 2: Deploy de código que escreve em ambas as colunas
// Fase 3: Preencher dados
module.exports = {
up: async (queryInterface) => {
await queryInterface.sequelize.query(`
UPDATE users
SET email_new = email
WHERE email_new IS NULL
`);
}
};
// Fase 4: Deploy de código que lê da nova coluna
// Fase 5: Remover coluna antiga
module.exports = {
up: async (queryInterface) => {
await queryInterface.removeColumn('users', 'email');
}
};
Migrações Entre Bancos de Dados
PostgreSQL para MySQL
// Lidar com diferenças
module.exports = {
up: async (queryInterface, Sequelize) => {
const dialectName = queryInterface.sequelize.getDialect();
if (dialectName === 'mysql') {
await queryInterface.createTable('users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
data: {
type: Sequelize.JSON // Tipo JSON do MySQL
}
});
} else if (dialectName === 'postgres') {
await queryInterface.createTable('users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
data: {
type: Sequelize.JSONB // Tipo JSONB do PostgreSQL
}
});
}
}
};
Recursos
- references/orm-switching.md: Guias de migração de ORM
- references/schema-migration.md: Padrões de transformação de schema
- references/data-transformation.md: Scripts de migração de dados
- references/rollback-strategies.md: Procedimentos de rollback
- assets/schema-migration-template.sql: Templates de migração SQL
- assets/data-migration-script.py: Utilitários de migração de dados
- scripts/test-migration.sh: Script de teste de migração
Melhores Práticas
- Sempre Forneça Rollback: Cada up() precisa de um down()
- Teste Migrações: Teste em staging primeiro
- Use Transações: Migrações atômicas quando possível
- Faça Backup Primeiro: Sempre faça backup antes de migrar
- Mudanças Pequenas: Divida em passos pequenos e incrementais
- Monitore: Observe erros durante o deployment
- Documente: Explique por que e como
- Idempotente: Migrações devem ser rexecutáveis
Armadilhas Comuns
- Não testar procedimentos de rollback
- Fazer mudanças que quebram compatibilidade sem estratégia de downtime
- Esquecer de lidar com valores NULL
- Não considerar desempenho de índices
- Ignorar restrições de chave estrangeira
- Migrar muitos dados de uma vez