Database migrations em times grandes: evitando conflitos de merge

1. O Problema dos Conflitos de Merge em Migrations

1.1. Causas comuns: numeração sequencial, dependências implícitas e alterações simultâneas

Em times grandes, múltiplos desenvolvedores trabalham simultaneamente em diferentes branches. O uso de numeração sequencial para migrations (ex: 001_create_users.sql, 002_add_email.sql) gera conflitos frequentes quando duas pessoas criam a migration 003 ao mesmo tempo. Dependências implícitas entre migrations — como uma migration que referencia uma coluna criada em outra — também causam quebras silenciosas. Alterações simultâneas no mesmo schema (ex: dois times modificando a tabela orders) levam a conflitos complexos de resolver.

1.2. Impacto no fluxo de desenvolvimento: bloqueios, retrabalho e perda de dados

Conflitos de merge em migrations podem bloquear deploys por horas, exigir retrabalho manual para renumerar arquivos e, em casos extremos, causar perda de dados se uma migration for aplicada na ordem errada. Times relatam que até 30% dos merges em projetos com muitas migrations envolvem algum tipo de conflito.

1.3. Diferença entre conflitos de schema e conflitos de dados

Conflitos de schema envolvem definições estruturais (colunas, tabelas, índices) e geralmente são resolvidos com merge de arquivos. Conflitos de dados ocorrem quando migrations de seed ou transformação de dados competem pelo mesmo registro — estes são mais perigosos e exigem coordenação cuidadosa.

2. Estratégias de Versionamento para Evitar Conflitos

2.1. Timestamps vs. sequenciais: vantagens do uso de carimbo de data/hora

Substituir numeração sequencial por timestamps (ex: 20250101120000_create_users.sql) reduz drasticamente conflitos, pois dois desenvolvedores raramente geram o mesmo timestamp. Ferramentas como Flyway e Alembic suportam nativamente esse formato.

-- Exemplo de nome de migration com timestamp
20250101120000_create_users.sql
20250101123045_add_email_to_users.sql
20250101124510_create_orders.sql

2.2. Prefixos por equipe ou módulo: isolando namespaces de migração

Atribuir prefixos por equipe (ex: team_a_001, team_b_001) ou por módulo (billing_001, auth_001) isola os namespaces. Cada equipe gerencia sua sequência independentemente, eliminando conflitos entre times.

-- Estrutura de diretórios com prefixos por módulo
migrations/
  billing/
    billing_001_create_invoices.sql
    billing_002_add_tax_column.sql
  auth/
    auth_001_create_users.sql
    auth_002_add_roles.sql

2.3. Hash-based versionamento: garantindo unicidade sem coordenação central

Usar hashes (SHA-256 do conteúdo) como identificador único garante que migrations diferentes sempre tenham nomes distintos. O hash é calculado automaticamente e não requer coordenação.

-- Exemplo de migration nomeada por hash
migrations/
  a1b2c3d4_create_users.sql
  e5f6g7h8_add_email_index.sql

3. Boas Práticas de Estruturação de Migrations

3.1. Migrações atômicas e reversíveis: cada migration foca em uma única mudança

Cada migration deve fazer exatamente uma alteração (ex: adicionar uma coluna, criar uma tabela) e ser reversível. Isso facilita rollbacks e reduz dependências.

-- Migration atômica: adicionar coluna
-- UP
ALTER TABLE users ADD COLUMN phone VARCHAR(20);

-- DOWN
ALTER TABLE users DROP COLUMN phone;

3.2. Separação entre schema e dados: migrations de estrutura vs. migrations de seed

Mantenha migrations de schema (DDL) separadas de migrations de dados (DML/seed). Schemas mudam com menos frequência e podem ser versionadas de forma mais rigorosa.

-- migrations/schema/20250101_create_users.sql
CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT);

-- migrations/seed/20250101_insert_admin_user.sql
INSERT INTO users (name) VALUES ('admin');

3.3. Uso de diretórios por sprint ou release: organização temporal e lógica

Organize migrations em diretórios por sprint (ex: sprint_01, sprint_02) ou release (v2.0, v2.1). Isso facilita a revisão e o rollback de releases inteiras.

migrations/
  release_v2.0/
    001_create_users.sql
    002_add_email.sql
  release_v2.1/
    001_add_phone.sql

4. Ferramentas e Automação para Prevenção de Conflitos

4.1. Linters e validadores de migrations: detecção de duplicatas e dependências quebradas

Ferramentas como flyway-validator ou alembic-check detectam migrations duplicadas, dependências circulares e quebras de schema antes do merge.

# Comando de validação com Flyway
flyway validate -url=jdbc:postgresql://localhost/mydb

4.2. Hooks de pre-commit e CI: checagem automática de ordem e integridade

Configure hooks de pre-commit que verificam se as migrations estão em ordem cronológica e se não há duplicatas. Na CI, execute validações completas.

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/flyway/flyway
    rev: v9.0.0
    hooks:
      - id: flyway-validate

4.3. Ferramentas de migração distribuída (ex: Flyway, Alembic, Liquibase) com suporte a merge

Flyway suporta migrations baseadas em timestamp e resolução automática de conflitos. Alembic permite ramificação e merge de branches de migração. Liquibase usa changelogs XML/YAML que facilitam merges.

# Exemplo de changelog Liquibase com merge-friendly structure
databaseChangeLog:
  - include:
      file: migrations/v1.0/changelog.xml
  - include:
      file: migrations/v1.1/changelog.xml

5. Processos de Revisão e Coordenação Entre Times

5.1. Pull requests específicos para migrations: revisão obrigatória e checklist

Crie PRs exclusivos para migrations, com checklist obrigatório: (1) migration é reversível? (2) nome segue convenção? (3) não há dependências quebradas?

5.2. Reuniões de alinhamento de schema: comunicação cross-team sobre mudanças impactantes

Realize reuniões semanais de alinhamento de schema onde times compartilham mudanças planejadas. Isso evita que dois times modifiquem a mesma tabela simultaneamente.

5.3. Feature flags e migrations condicionais: evitando bloqueios em deploys simultâneos

Use feature flags para controlar quando uma migration é aplicada. Migrações condicionais (ex: IF NOT EXISTS) permitem que o mesmo script seja executado múltiplas vezes sem erro.

-- Migration condicional
ALTER TABLE users ADD COLUMN IF NOT EXISTS phone VARCHAR(20);

6. Estratégias de Merge e Resolução de Conflitos

6.1. Merge hierárquico: resolver conflitos de migrations antes de outros arquivos

Resolva conflitos de migrations primeiro, antes de outros arquivos de código. Isso evita que problemas de schema bloqueiem o merge de funcionalidades.

6.2. Rebase vs. merge: quando usar cada um para minimizar conflitos

Use rebase para branches curtos com poucas migrations — isso mantém um histórico linear. Use merge para branches longos com muitas migrations, pois rebase pode causar retrabalho excessivo.

6.3. Ferramentas de diff específicas para SQL: visualizando mudanças de schema

Ferramentas como pgdiff ou sqldiff mostram diferenças entre schemas de forma clara, facilitando a identificação de conflitos.

# Comparando schemas com pgdiff
pgdiff --source old_schema.sql --target new_schema.sql

7. Monitoramento e Rollback em Produção

7.1. Logs e métricas de execução de migrations: identificando falhas em tempo real

Implemente logging detalhado de cada migration executada, incluindo tempo de execução, status e erros. Use métricas para detectar migrations lentas ou falhas recorrentes.

7.2. Estratégias de rollback seguro: downgrade automático e manual

Mantenha scripts de downgrade para cada migration. Em produção, prefira rollbacks automáticos com validação prévia em staging.

-- Script de downgrade automático
-- DOWN
ALTER TABLE users DROP COLUMN phone;

7.3. Testes de integração com migrations: validação em ambiente de staging antes do deploy

Execute todas as migrations em staging com dados de teste realistas antes de aplicar em produção. Automatize testes que verificam a integridade dos dados após cada migration.

Referências