--- title: "Migração CNPJ Alfanumérico - 100 Milhões de Registros" slug: "cnpj-migration-database" summary: "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." client: "Empresa de Cobrança" industry: "Cobrança & Serviços Financeiros" timeline: "Em execução" role: "Database Architect & Tech Lead" image: "" tags: - SQL Server - Database Migration - CNPJ - Performance Optimization - Batch Processing - Big Data featured: true order: 4 date: 2024-11-01 seo_title: "Migração CNPJ Alfanumérico - 100M Registros | Carneiro Tech" seo_description: "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." seo_keywords: "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:** 1. 🔴 **Tabelas com 8M+ linhas** usando `BIGINT` para CNPJ 2. 🔴 **90 milhões de registros** em tabela de transações 3. 🔑 **CNPJ como chave primária** em algumas tabelas 4. 🔗 **Foreign keys** relacionando múltiplas tabelas 5. ⚠️ **Impossibilidade de downtime prolongado** (operação 24/7) 6. 💾 **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):** ```sql -- ❌ 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 ```sql -- 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) ```sql -- 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 ```sql -- 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) ```sql -- 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 ```sql -- 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 ```sql -- 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 1. **Backup completo** de produção 2. **Execução em produção** (ambiente 24/7) 3. **Monitoramento em tempo real** durante migração 4. **Validação pós-migração** (integridade, performance) 5. **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 ```sql -- Log example Processed: 10.000.000 rows. Batch: 100.000 Elapsed time: 3600 seconds (10% complete, ~9h remaining) ``` --- ## Performance Optimizations ### Otimizações Implementadas 1. **Índice temporário WHERE NULL** - Acelera lookup de registros não migrados - Removido após conclusão 2. **Batch size otimizado** - Balanceado entre performance e lock time 3. **Transaction log management** ```sql -- Verificar crescimento do log DBCC SQLPERF(LOGSPACE); -- Ajustar recovery model (se permitido) ALTER DATABASE MyDatabase SET RECOVERY SIMPLE; ``` 4. **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. [Precisa migrar volumes massivos de dados? Entre em contato](#contact)