470 lines
15 KiB
Markdown
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)
|