15 KiB
| title | slug | summary | client | industry | timeline | role | image | tags | featured | order | date | seo_title | seo_description | seo_keywords | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Migração CNPJ Alfanumérico - 100 Milhões de Registros | cnpj-migration-database | Execução de migração massiva de CNPJ numérico para alfanumérico em banco de dados com ~100M registros, usando estratégia de commits faseados para evitar travamento. | Empresa de Cobrança | Cobrança & Serviços Financeiros | Em execução | Database Architect & Tech Lead |
|
true | 4 | 2024-11-01 | Migração CNPJ Alfanumérico - 100M Registros | Carneiro Tech | Case de migração massiva de CNPJ em banco de dados com 100 milhões de registros usando commits faseados e otimizações de performance. | database migration, SQL Server, CNPJ, batch processing, performance optimization, phased commits |
Overview
Uma empresa de cobrança que trabalha com bancos de dados de informação transitória (sem software proprietário) precisa adaptar seus sistemas ao novo formato de CNPJ alfanumérico brasileiro.
Desafio principal: Migrar ~100 milhões de registros em tabelas com colunas BIGINT e NUMERIC para VARCHAR, sem travar o banco de dados em produção.
Status: Projeto em execução (preparação de scripts de migração).
Challenge
Volume Massivo de Dados
Contexto da empresa:
- Empresa de cobrança (não desenvolve software próprio)
- Trabalha com dados transitórios (alta rotatividade)
- Banco de dados SQL Server com volume crítico
Análise inicial revelou:
| Tabela | Coluna | Tipo Atual | Registros | Tamanho |
|---|---|---|---|---|
| Devedores | CNPJ_Devedor | BIGINT | 8.000.000 | 60 GB |
| Transações | CNPJ_Pagador | NUMERIC(14) | 90.000.000 | 1.2 TB |
| Empresas | CNPJ_Empresa | BIGINT | 2.500.000 | 18 GB |
| TOTAL | - | - | ~100.000.000 | ~1.3 TB |
Problemas identificados:
- 🔴 Tabelas com 8M+ linhas usando
BIGINTpara CNPJ - 🔴 90 milhões de registros em tabela de transações
- 🔑 CNPJ como chave primária em algumas tabelas
- 🔗 Foreign keys relacionando múltiplas tabelas
- ⚠️ Impossibilidade de downtime prolongado (operação 24/7)
- 💾 Restrições de espaço em disco (precisa estratégia eficiente)
Strategic Decision: Phased Commits
Por que NÃO fazer ALTER COLUMN direto?
Abordagem ingênua (NÃO funciona):
-- ❌ NUNCA FAÇA ISSO EM TABELAS GRANDES
ALTER TABLE Transacoes
ALTER COLUMN CNPJ_Pagador VARCHAR(18);
Problemas:
- 🔒 Trava a tabela inteira durante a conversão
- ⏱️ Pode levar horas/dias em tabelas grandes
- 💥 Bloqueia todas as operações (INSERT, UPDATE, SELECT)
- 🚨 Risco de timeout ou falha no meio da operação
- 🔙 Rollback complexo se algo der errado
Estratégia Escolhida: Column Swap com Commits Faseados
Baseado em experiência anterior, decidi usar abordagem gradual:
┌─────────────────────────────────────────────┐
│ 1. Criar nova coluna VARCHAR no FINAL │
│ (operação rápida, não bloqueia tabela) │
└─────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ 2. UPDATE em lotes (commits faseados) │
│ - 100k registros por vez │
│ - Pausa entre lotes (evita contenção) │
└─────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ 3. Remover PKs e FKs │
│ (após 100% migrado) │
└─────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ 4. Renomear colunas (swap) │
│ - CNPJ → CNPJ_Old │
│ - CNPJ_New → CNPJ │
└─────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ 5. Recriar PKs/FKs com nova coluna │
└─────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ 6. Validação e exclusão da coluna antiga │
└─────────────────────────────────────────────┘
Por que essa abordagem?
✅ Sem lock de tabela completa (operação incremental) ✅ Pode pausar/retomar a qualquer momento ✅ Monitoramento de progresso em tempo real ✅ Rollback simples (basta dropar nova coluna) ✅ Minimiza impacto em produção (commits pequenos)
Decisão tomada baseada em:
- 📚 Experiência anterior com migrações de grande volume
- 🔍 Conhecimento de locks do SQL Server
- 🎯 Necessidade de zero downtime
Nota: Essa decisão foi tomada sem consultar IA - baseada puramente em experiência prática de projetos anteriores.
Implementation Details
Fase 1: Criar Nova Coluna
-- Operação rápida (metadata change apenas)
ALTER TABLE Transacoes
ADD CNPJ_Pagador_New VARCHAR(18) NULL;
-- Adiciona índice temporário para acelerar lookups
CREATE NONCLUSTERED INDEX IX_Temp_CNPJ_New
ON Transacoes(CNPJ_Pagador_New)
WHERE CNPJ_Pagador_New IS NULL;
Tempo estimado: ~1 segundo (independente do tamanho da tabela)
Fase 2: Migração em Lotes (Core Strategy)
-- Script de migração com commits faseados
DECLARE @BatchSize INT = 100000; -- 100k registros por lote
DECLARE @RowsAffected INT = 1;
DECLARE @TotalProcessed INT = 0;
DECLARE @StartTime DATETIME = GETDATE();
WHILE @RowsAffected > 0
BEGIN
BEGIN TRANSACTION;
-- Atualiza lote de 100k registros ainda não migrados
UPDATE TOP (@BatchSize) Transacoes
SET CNPJ_Pagador_New = RIGHT('00000000000000' + CAST(CNPJ_Pagador AS VARCHAR), 14)
WHERE CNPJ_Pagador_New IS NULL;
SET @RowsAffected = @@ROWCOUNT;
SET @TotalProcessed = @TotalProcessed + @RowsAffected;
COMMIT TRANSACTION;
-- Log de progresso
PRINT 'Processed: ' + CAST(@TotalProcessed AS VARCHAR) + ' rows. Batch: ' + CAST(@RowsAffected AS VARCHAR);
PRINT 'Elapsed time: ' + CAST(DATEDIFF(SECOND, @StartTime, GETDATE()) AS VARCHAR) + ' seconds';
-- Pausa entre lotes (reduz contenção)
WAITFOR DELAY '00:00:01'; -- 1 segundo entre lotes
END;
PRINT 'Migration completed! Total rows: ' + CAST(@TotalProcessed AS VARCHAR);
Parâmetros configuráveis:
@BatchSize: 100k (balanceado entre performance e lock time)- Muito pequeno = muitas transações, overhead
- Muito grande = lock prolongado, impacto em prod
WAITFOR DELAY: 1 segundo (dá tempo de outras queries rodarem)
Estimativas de tempo:
| Registros | Batch Size | Tempo Estimado |
|---|---|---|
| 8.000.000 | 100.000 | ~2-3 horas |
| 90.000.000 | 100.000 | ~20-24 horas |
Vantagens:
- ✅ Não trava aplicação
- ✅ Outras queries conseguem rodar entre os lotes
- ✅ Pode pausar (Ctrl+C) e retomar depois (WHERE NULL pega de onde parou)
- ✅ Log de progresso em tempo real
Fase 3: Remoção de Constraints
-- Identifica todas as PKs e FKs envolvendo a coluna
SELECT name
FROM sys.key_constraints
WHERE type = 'PK'
AND parent_object_id = OBJECT_ID('Transacoes')
AND COL_NAME(parent_object_id, parent_column_id) = 'CNPJ_Pagador';
-- Remove PKs
ALTER TABLE Transacoes
DROP CONSTRAINT PK_Transacoes_CNPJ;
-- Remove FKs (tabelas que referenciam)
ALTER TABLE Pagamentos
DROP CONSTRAINT FK_Pagamentos_Transacoes;
Tempo estimado: ~10 minutos (depende de quantas constraints existem)
Fase 4: Column Swap (Renomeação)
-- Renomeia coluna antiga para _Old
EXEC sp_rename 'Transacoes.CNPJ_Pagador', 'CNPJ_Pagador_Old', 'COLUMN';
-- Renomeia nova coluna para o nome original
EXEC sp_rename 'Transacoes.CNPJ_Pagador_New', 'CNPJ_Pagador', 'COLUMN';
-- Altera para NOT NULL (após validação de 100% preenchido)
ALTER TABLE Transacoes
ALTER COLUMN CNPJ_Pagador VARCHAR(18) NOT NULL;
Tempo estimado: ~1 segundo (metadata change)
Fase 5: Recriação de Constraints
-- Recria PK com nova coluna VARCHAR
ALTER TABLE Transacoes
ADD CONSTRAINT PK_Transacoes_CNPJ
PRIMARY KEY CLUSTERED (CNPJ_Pagador);
-- Recria FKs
ALTER TABLE Pagamentos
ADD CONSTRAINT FK_Pagamentos_Transacoes
FOREIGN KEY (CNPJ_Pagador) REFERENCES Transacoes(CNPJ_Pagador);
Tempo estimado: ~30-60 minutos (depende do volume)
Fase 6: Validação e Limpeza
-- Valida que 100% foi migrado
SELECT COUNT(*)
FROM Transacoes
WHERE CNPJ_Pagador IS NULL OR CNPJ_Pagador = '';
-- Valida integridade referencial
DBCC CHECKCONSTRAINTS WITH ALL_CONSTRAINTS;
-- Se tudo OK, remove coluna antiga
ALTER TABLE Transacoes
DROP COLUMN CNPJ_Pagador_Old;
-- Remove índice temporário
DROP INDEX IX_Temp_CNPJ_New ON Transacoes;
Customização do Processo CNPJ Fast
Diferenças vs. Processo Original
O processo CNPJ Fast original foi reestruturado para este cliente:
Mudanças principais:
| Aspecto | CNPJ Fast Original | Cliente (Customizado) |
|---|---|---|
| Foco | Aplicações + DB | Apenas DB (sem software próprio) |
| Discovery | Inventário de apps | Apenas análise de schema |
| Execução | Múltiplas aplicações | Scripts SQL massivos |
| Batch Size | 50k-100k | 100k (otimizado para volume) |
| Monitoramento | Manual + ferramentas | Logs SQL em tempo real |
| Rollback | Processo complexo | Simples (DROP COLUMN) |
Motivo da reestruturação:
- Cliente não tem aplicações próprias (apenas consome dados)
- Foco 100% em otimização de banco
- Volume muito maior que casos típicos (100M vs ~10M)
Tech Stack
SQL Server T-SQL Batch Processing Performance Tuning Database Optimization Migration Scripts Phased Commits Index Optimization Constraint Management
Key Decisions & Trade-offs
Por que 100k por batch?
Testes de performance:
| Batch Size | Tempo/Batch | Lock Duration | Contenção |
|---|---|---|---|
| 10.000 | 2s | Baixo | ✅ Mínimo |
| 50.000 | 8s | Médio | ✅ Aceitável |
| 100.000 | 15s | Médio | ✅ Balanceado |
| 500.000 | 90s | Alto | ❌ Impacto em prod |
| 1.000.000 | 180s | Muito alto | ❌ Inaceitável |
Escolha: 100k oferece melhor balanço entre performance e impacto.
Por que criar coluna no FINAL?
SQL Server internals:
- Adicionar coluna no final = metadata change (rápido)
- Adicionar no meio = reescrita de páginas (lento)
- Para tabelas grandes, posição importa
Por que WAITFOR DELAY de 1 segundo?
Sem delay:
- ❌ Batch processing consome 100% do I/O
- ❌ Queries de aplicação ficam lentas
- ❌ Lock escalation pode ocorrer
Com delay de 1s:
- ✅ Outras queries têm janela para executar
- ✅ I/O distribuído
- ✅ Experiência do usuário preservada
Trade-off: Migração leva +1s por batch (~25% mais lenta), mas sistema permanece responsivo.
Current Status & Next Steps
Status Atual (Dezembro 2024)
📝 Fase de Preparação:
- ✅ Discovery completo (100M registros identificados)
- ✅ Scripts de migração desenvolvidos
- ✅ Testes em ambiente de homologação
- 🔄 Validação de performance
- ⏳ Aguardando janela de manutenção para produção
Próximos Passos
- Backup completo de produção
- Execução em produção (ambiente 24/7)
- Monitoramento em tempo real durante migração
- Validação pós-migração (integridade, performance)
- Documentação de lessons learned
Lessons Learned (Até Agora)
1. Experiência Anterior Vale Ouro
Decisão de usar phased commits veio de experiência prática em projetos anteriores, não de documentação ou IA.
Situações similares anteriores:
- Migração de dados em e-commerce (50M registros)
- Conversão de encoding (UTF-8 em 100M+ rows)
- Particionamento de tabelas históricas
2. "Measure Twice, Cut Once"
Antes de executar em produção:
- ✅ Testes exaustivos em homologação
- ✅ Scripts validados e revisados
- ✅ Rollback testado
- ✅ Estimativas de tempo confirmadas
Tempo de preparação: 3 semanas Tempo de execução: Estimado em 48 horas
Ratio: 10:1 (preparação vs execução)
3. Customização > One-Size-Fits-All
O processo CNPJ Fast original precisou ser reestruturado para este cliente.
Lição: Processos devem ser:
- Estruturados o suficiente para repetir
- Flexíveis o suficiente para adaptar
4. Monitoramento é Crucial
Scripts com log detalhado de progresso permitem:
- Estimar tempo restante
- Identificar gargalos
- Pausar/retomar com confiança
- Reportar status para stakeholders
-- Log example
Processed: 10.000.000 rows. Batch: 100.000
Elapsed time: 3600 seconds (10% complete, ~9h remaining)
Performance Optimizations
Otimizações Implementadas
-
Índice temporário WHERE NULL
- Acelera lookup de registros não migrados
- Removido após conclusão
-
Batch size otimizado
- Balanceado entre performance e lock time
-
Transaction log management
-- Verificar crescimento do log DBCC SQLPERF(LOGSPACE); -- Ajustar recovery model (se permitido) ALTER DATABASE MyDatabase SET RECOVERY SIMPLE; -
Execução em horário de menor carga
- Janela de manutenção noturna
- Final de semana (se possível)
Resultado esperado: Migração de 100 milhões de registros em ~48 horas, sem downtime significativo e com possibilidade de rollback rápido.