CarneiroTech/Content/Cases/pt/cnpj-migration-database.md

470 lines
15 KiB
Markdown

---
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)