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