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