Pular para conteúdo

RFC-006: Estratégia de Database Migrations (Django + Alembic)

Campo Valor
Status Draft
Author Time de Tecnologia
Created 2026-02-05
Updated 2026-02-05
Reunião A ser agendada
Deciders Todo o time técnico

Status

🟡 Draft - RFC em elaboração, aguardando apresentação

Contexto e Problema

Processo de migrations documentado para Alembic em docs/infrastructure/database/migrations.md, mas falta:

  • Estratégia para Django migrations (Django ORM)
  • Sincronização entre Django e Alembic migrations
  • Ordem de execução quando ambos mudam schema
  • Rollback procedures formalizadas e testadas
  • Testing obrigatório de migrations antes de produção
  • Backup automático antes de aplicar em produção

Por que resolver isso agora?

  • Projeto usa Django E FastAPI em coexistência
  • Ambos precisam manipular schema do PostgreSQL
  • Risco de conflitos se não coordenados
  • Downtime ou data loss por migrations mal executadas
  • Processo de rollback não está testado

Impacto de não resolver

  • Conflitos entre Django e Alembic migrations
  • Downtime em produção por migrations problemáticas
  • Data loss por migrations irreversíveis
  • Confusão sobre qual tool usar quando
  • Incidentes em produção evitáveis

Documentação relacionada: - docs/infrastructure/database/migrations.md - Processo Alembic - docs/infrastructure/database/backup-restore.md - Backup procedures - docs/infrastructure/database/rollback.md - Rollback procedures - docs/infrastructure/database/overview.md - Arquitetura database - docs/architecture/database-schema.md - Schema e convenções

Proposta de Solução

Estratégia unificada para migrations Django + Alembic com source of truth claro.

Divisão de Responsabilidades

Django migrations (models Django): - Tabelas gerenciadas pelo Django Admin - User management (auth, permissions) - Tabelas com ForeignKey para Django models - Qualquer model com class Meta: managed = True

Alembic migrations (FastAPI): - Tabelas exclusivas do FastAPI - Tabelas sem relação com Django - Índices e otimizações de performance - Triggers e functions PostgreSQL

Shared Database: - PostgreSQL único para Django e FastAPI - Django migrations são source of truth do schema - Alembic sincroniza a partir do schema Django

Ordem de Execução

Desenvolvimento Local:

# 1. Criar/atualizar models Django
# app/models.py

# 2. Gerar Django migration
python manage.py makemigrations

# 3. Aplicar Django migration localmente
python manage.py migrate

# 4. Se FastAPI precisa de mudanças adicionais:
#    Gerar Alembic migration (sync com Django schema)
alembic revision --autogenerate -m "sync with django"

# 5. Aplicar Alembic migration
alembic upgrade head

Staging/Production:

# Ordem obrigatória:
1. Backup database
2. Django migrations (python manage.py migrate)
3. Alembic migrations (alembic upgrade head)
4. Smoke tests
5. Rollback se falhar

Safe Migration Patterns

Adicionar coluna (Django):

# Migration 1: Add nullable column
class Migration(migrations.Migration):
    operations = [
        migrations.AddField(
            model_name='user',
            name='phone',
            field=models.CharField(max_length=20, null=True, blank=True),
        ),
    ]

# Deploy app code that uses phone

# Migration 2 (later): Make NOT NULL if needed
class Migration(migrations.Migration):
    operations = [
        # First populate existing rows
        migrations.RunPython(populate_phone_defaults),
        # Then make NOT NULL
        migrations.AlterField(
            model_name='user',
            name='phone',
            field=models.CharField(max_length=20, null=False),
        ),
    ]

Renomear coluna (multi-step):

# Step 1: Add new column (nullable)
operations = [
    migrations.AddField('user', 'full_name', models.CharField(max_length=255, null=True)),
    migrations.RunPython(copy_name_to_full_name),
]

# Step 2: Deploy code usando full_name

# Step 3: Remove old column
operations = [
    migrations.RemoveField('user', 'name'),
]

Adicionar índice (não bloqueia):

# Django
from django.contrib.postgres.operations import AddIndexConcurrently

class Migration(migrations.Migration):
    atomic = False  # CONCURRENT requer non-atomic

    operations = [
        AddIndexConcurrently(
            model_name='user',
            index=models.Index(fields=['email'], name='user_email_idx'),
        ),
    ]

# Alembic (equivalente)
def upgrade():
    op.create_index(
        'user_email_idx',
        'user',
        ['email'],
        postgresql_concurrently=True
    )

Testing de Migrations

Obrigatório antes de produção:

# 1. Staging: aplicar migrations
python manage.py migrate
alembic upgrade head

# 2. Rodar smoke tests
pytest tests/integration/test_migrations.py

# 3. Testar rollback
python manage.py migrate app_name <previous_migration>
alembic downgrade -1

# 4. Re-aplicar (testar idempotência)
python manage.py migrate
alembic upgrade head

Tests automatizados:

# tests/integration/test_migrations.py
def test_django_migrations_apply_cleanly(db):
    """Test all Django migrations apply without errors."""
    call_command('migrate', verbosity=0)

def test_alembic_migrations_apply_cleanly():
    """Test all Alembic migrations apply without errors."""
    alembic.command.upgrade(alembic_cfg, "head")

def test_migrations_are_reversible():
    """Test migrations can be rolled back."""
    # Apply
    call_command('migrate')
    alembic.command.upgrade(alembic_cfg, "head")

    # Rollback
    call_command('migrate', 'app_name', '0001')
    alembic.command.downgrade(alembic_cfg, "-1")

    # Re-apply
    call_command('migrate')
    alembic.command.upgrade(alembic_cfg, "head")

Backup Automático

Staging (antes de cada migration):

# GitHub Actions
- name: Backup staging DB
  run: |
    pg_dump $DATABASE_URL > backup_$(date +%Y%m%d_%H%M%S).sql
    aws s3 cp backup_*.sql s3://backups/staging/

Production (crítico):

- name: Backup production DB
  run: |
    # RDS snapshot (rápido, ~5 min)
    aws rds create-db-snapshot \
      --db-instance-identifier prod-db \
      --db-snapshot-identifier "pre-migration-$(date +%Y%m%d-%H%M%S)"

    # Aguardar snapshot completar
    aws rds wait db-snapshot-completed \
      --db-snapshot-identifier "pre-migration-$(date +%Y%m%d-%H%M%S)"

Rollback Procedures

Se migration falha:

# 1. Rollback Django
python manage.py migrate app_name <previous_migration_number>

# 2. Rollback Alembic
alembic downgrade -1

# 3. Restore backup se necessário
pg_restore -d $DATABASE_URL backup.sql
# ou
aws rds restore-db-instance-from-db-snapshot

Rollback limits: - Migrations devem ser reversíveis - Data migrations podem não ser 100% reversíveis - Documentar limitações no migration file - Testar rollback em staging primeiro

Alternativas Consideradas

Opção 1: Apenas Django Migrations

Prós: - Single source of truth - Mais simples - Menos ferramentas

Contras: - FastAPI precisa de Django como dependência - Coupling desnecessário - FastAPI não usa Django models

Por que não escolhemos: FastAPI deve ser independente do Django.

Opção 2: Apenas Alembic

Prós: - Single source of truth - FastAPI-native - Mais control sobre SQL

Contras: - Django precisa de migrations próprias para admin funcionar - Perder funcionalidades Django (signals, etc.) - Refactor grande de models existentes

Por que não escolhemos: Django admin depende de migrations do Django.

Opção 3: Databases separadas

Prós: - Zero conflito - Scaling independente

Contras: - Data duplication - Sync complexo - Custo 2x - Queries cross-database impossíveis

Por que não escolhemos: Complexidade não justificada para nosso tamanho.

Análise de Impacto

Impacto Técnico

  • Django migrations primeiro, Alembic depois
  • Testing obrigatório em staging
  • Backup automático em produção
  • Rollback procedures claras

Impacto em Negócio

  • ✅ Zero downtime em migrations
  • ✅ Data loss prevenido
  • ✅ Rollback seguro e testado
  • ⚠️ Deploy ligeiramente mais lento (backups)

Riscos

Risco: Conflito entre Django e Alembic

Mitigação: Django é source of truth, Alembic sincroniza. Review cuidadoso de migrations.

Plano de Implementação

Fase 1: Documentação (Semana 1)

  • Documentar divisão de responsabilidades
  • Criar templates de migrations seguras
  • Documentar rollback procedures

Fase 2: Automação (Semana 2)

  • CI/CD: backup automático antes de migrations
  • CI/CD: rodar migrations em ordem correta
  • CI/CD: smoke tests pós-migration

Fase 3: Testing (Semana 3)

  • Criar tests de migrations
  • Testar rollback em staging
  • Documentar limitações

Fase 4: Treinamento (Semana 4)

  • Sessão com time sobre processo
  • Pair programming em próximas migrations
  • Postmortem de incidentes anteriores

Métricas de Sucesso

Após 1 mês: - ✅ Zero conflitos Django/Alembic - ✅ 100% migrations testadas em staging - ✅ 100% backups antes de prod migrations

Após 3 meses: - ✅ Zero incidentes de migration em prod - ✅ Tempo médio de migration < 5 min - ✅ Rollback testado e funcional

Referências