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

470 lines
15 KiB
Markdown

---
title: "Migración CNPJ Alfanumérico - 100 Millones de Registros"
slug: "cnpj-migration-database"
summary: "Ejecución de migración masiva de CNPJ numérico a alfanumérico en base de datos con ~100M registros, usando estrategia de commits faseados para evitar bloqueo."
client: "Empresa de Cobranza"
industry: "Cobranza & Servicios Financieros"
timeline: "En ejecución"
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: "Migración CNPJ Alfanumérico - 100M Registros | Carneiro Tech"
seo_description: "Caso de migración masiva de CNPJ en base de datos con 100 millones de registros usando commits faseados y optimizaciones de performance."
seo_keywords: "database migration, SQL Server, CNPJ, batch processing, performance optimization, phased commits"
---
## Descripción General
Una empresa de cobranza que trabaja con bases de datos de información transitoria (sin software propietario) necesita adaptar sus sistemas al nuevo formato de **CNPJ alfanumérico** brasileño.
**Desafío principal:** Migrar ~**100 millones de registros** en tablas con columnas `BIGINT` y `NUMERIC` a `VARCHAR`, sin bloquear la base de datos en producción.
**Estado:** Proyecto en ejecución (preparación de scripts de migración).
---
## Desafío
### Volumen Masivo de Datos
**Contexto de la empresa:**
- Empresa de cobranza (no desarrolla software propio)
- Trabaja con **datos transitorios** (alta rotación)
- Base de datos SQL Server con volumen crítico
**Análisis inicial reveló:**
| Tabla | Columna | Tipo Actual | Registros | Tamaño |
|--------|--------|------------|-----------|---------|
| Deudores | CNPJ_Deudor | BIGINT | 8.000.000 | 60 GB |
| Transacciones | 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. 🔴 **Tablas con 8M+ líneas** usando `BIGINT` para CNPJ
2. 🔴 **90 millones de registros** en tabla de transacciones
3. 🔑 **CNPJ como clave primaria** en algunas tablas
4. 🔗 **Foreign keys** relacionando múltiples tablas
5. ⚠️ **Imposibilidad de downtime prolongado** (operación 24/7)
6. 💾 **Restricciones de espacio** en disco (necesita estrategia eficiente)
---
## Decisión Estratégica: Phased Commits
### ¿Por qué NO hacer ALTER COLUMN directo?
**Enfoque ingenuo (NO funciona):**
```sql
-- ❌ NUNCA HAGA ESTO EN TABLAS GRANDES
ALTER TABLE Transacciones
ALTER COLUMN CNPJ_Pagador VARCHAR(18);
```
**Problemas:**
- 🔒 Bloquea la tabla entera durante la conversión
- ⏱️ Puede tomar horas/días en tablas grandes
- 💥 Bloquea todas las operaciones (INSERT, UPDATE, SELECT)
- 🚨 Riesgo de timeout o falla en medio de la operación
- 🔙 Rollback complejo si algo sale mal
---
### Estrategia Elegida: Column Swap con Commits Faseados
**Basado en experiencia anterior**, decidí usar enfoque gradual:
```
┌─────────────────────────────────────────────┐
│ 1. Crear nueva columna VARCHAR al FINAL │
│ (operación rápida, no bloquea tabla) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 2. UPDATE en lotes (commits faseados) │
│ - 100k registros a la vez │
│ - Pausa entre lotes (evita contención) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 3. Remover PKs y FKs │
│ (tras 100% migrado) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 4. Renombrar columnas (swap) │
│ - CNPJ → CNPJ_Old │
│ - CNPJ_New → CNPJ │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 5. Recrear PKs/FKs con nueva columna │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 6. Validación y eliminación columna vieja │
└─────────────────────────────────────────────┘
```
**¿Por qué este enfoque?**
**Sin lock de tabla completa** (operación incremental)
**Puede pausar/reanudar** en cualquier momento
**Monitoreo de progreso** en tiempo real
**Rollback simple** (basta eliminar nueva columna)
**Minimiza impacto en producción** (commits pequeños)
**Decisión tomada basada en:**
- 📚 Experiencia anterior con migraciones de gran volumen
- 🔍 Conocimiento de locks de SQL Server
- 🎯 Necesidad de zero downtime
**Nota:** Esta decisión fue tomada **sin consultar IA** - basada puramente en experiencia práctica de proyectos anteriores.
---
## Detalles de Implementación
### Fase 1: Crear Nueva Columna
```sql
-- Operación rápida (metadata change solamente)
ALTER TABLE Transacciones
ADD CNPJ_Pagador_New VARCHAR(18) NULL;
-- Agrega índice temporal para acelerar lookups
CREATE NONCLUSTERED INDEX IX_Temp_CNPJ_New
ON Transacciones(CNPJ_Pagador_New)
WHERE CNPJ_Pagador_New IS NULL;
```
**Tiempo estimado:** ~1 segundo (independiente del tamaño de la tabla)
---
### Fase 2: Migración en Lotes (Core Strategy)
```sql
-- Script de migración con 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;
-- Actualiza lote de 100k registros aún no migrados
UPDATE TOP (@BatchSize) Transacciones
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 progreso
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 (reduce contención)
WAITFOR DELAY '00:00:01'; -- 1 segundo entre lotes
END;
PRINT 'Migration completed! Total rows: ' + CAST(@TotalProcessed AS VARCHAR);
```
**Parámetros configurables:**
- `@BatchSize`: 100k (balanceado entre performance y lock time)
- Muy pequeño = muchas transacciones, overhead
- Muy grande = lock prolongado, impacto en prod
- `WAITFOR DELAY`: 1 segundo (da tiempo a otras queries para ejecutar)
**Estimaciones de tiempo:**
| Registros | Batch Size | Tiempo Estimado |
|-----------|------------|----------------|
| 8.000.000 | 100.000 | ~2-3 horas |
| 90.000.000 | 100.000 | ~20-24 horas |
**Ventajas:**
- ✅ No bloquea aplicación
- ✅ Otras queries pueden ejecutar entre los lotes
- ✅ Puede pausar (Ctrl+C) y reanudar después (WHERE NULL toma desde donde paró)
- ✅ Log de progreso en tiempo real
---
### Fase 3: Remoción de Constraints
```sql
-- Identifica todas las PKs y FKs que involucran la columna
SELECT name
FROM sys.key_constraints
WHERE type = 'PK'
AND parent_object_id = OBJECT_ID('Transacciones')
AND COL_NAME(parent_object_id, parent_column_id) = 'CNPJ_Pagador';
-- Remueve PKs
ALTER TABLE Transacciones
DROP CONSTRAINT PK_Transacciones_CNPJ;
-- Remueve FKs (tablas que referencian)
ALTER TABLE Pagos
DROP CONSTRAINT FK_Pagos_Transacciones;
```
**Tiempo estimado:** ~10 minutos (depende de cuántas constraints existen)
---
### Fase 4: Column Swap (Renombramiento)
```sql
-- Renombra columna antigua a _Old
EXEC sp_rename 'Transacciones.CNPJ_Pagador', 'CNPJ_Pagador_Old', 'COLUMN';
-- Renombra nueva columna al nombre original
EXEC sp_rename 'Transacciones.CNPJ_Pagador_New', 'CNPJ_Pagador', 'COLUMN';
-- Altera a NOT NULL (tras validación de 100% completado)
ALTER TABLE Transacciones
ALTER COLUMN CNPJ_Pagador VARCHAR(18) NOT NULL;
```
**Tiempo estimado:** ~1 segundo (metadata change)
---
### Fase 5: Recreación de Constraints
```sql
-- Recrea PK con nueva columna VARCHAR
ALTER TABLE Transacciones
ADD CONSTRAINT PK_Transacciones_CNPJ
PRIMARY KEY CLUSTERED (CNPJ_Pagador);
-- Recrea FKs
ALTER TABLE Pagos
ADD CONSTRAINT FK_Pagos_Transacciones
FOREIGN KEY (CNPJ_Pagador) REFERENCES Transacciones(CNPJ_Pagador);
```
**Tiempo estimado:** ~30-60 minutos (depende del volumen)
---
### Fase 6: Validación y Limpieza
```sql
-- Valida que 100% fue migrado
SELECT COUNT(*)
FROM Transacciones
WHERE CNPJ_Pagador IS NULL OR CNPJ_Pagador = '';
-- Valida integridad referencial
DBCC CHECKCONSTRAINTS WITH ALL_CONSTRAINTS;
-- Si todo OK, remueve columna antigua
ALTER TABLE Transacciones
DROP COLUMN CNPJ_Pagador_Old;
-- Remueve índice temporal
DROP INDEX IX_Temp_CNPJ_New ON Transacciones;
```
---
## Personalización del Proceso CNPJ Fast
### Diferencias vs. Proceso Original
El proceso **CNPJ Fast** original fue **reestructurado** para este cliente:
**Cambios principales:**
| Aspecto | CNPJ Fast Original | Cliente (Personalizado) |
|---------|-------------------|---------------------|
| **Foco** | Aplicaciones + DB | Solo DB (sin software propio) |
| **Discovery** | Inventario de apps | Solo análisis de schema |
| **Ejecución** | Múltiples aplicaciones | Scripts SQL masivos |
| **Batch Size** | 50k-100k | 100k (optimizado para volumen) |
| **Monitoreo** | Manual + herramientas | Logs SQL en tiempo real |
| **Rollback** | Proceso complejo | Simple (DROP COLUMN) |
**Motivo de la reestructuración:**
- Cliente no tiene aplicaciones propias (solo consume datos)
- Foco 100% en optimización de base de datos
- Volumen mucho mayor 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`
---
## Decisiones Clave & Trade-offs
### ¿Por qué 100k por batch?
**Pruebas de performance:**
| Batch Size | Tiempo/Batch | Lock Duration | Contención |
|------------|-------------|---------------|-----------|
| 10.000 | 2s | Bajo | ✅ Mínimo |
| 50.000 | 8s | Medio | ✅ Aceptable |
| **100.000** | 15s | **Medio** | **✅ Balanceado** |
| 500.000 | 90s | Alto | ❌ Impacto en prod |
| 1.000.000 | 180s | Muy alto | ❌ Inaceptable |
**Elección:** 100k ofrece mejor balance entre performance e impacto.
---
### ¿Por qué crear columna al FINAL?
**Internals de SQL Server:**
- Agregar columna al final = metadata change (rápido)
- Agregar en medio = reescritura de páginas (lento)
- Para tablas grandes, posición importa
---
### ¿Por qué WAITFOR DELAY de 1 segundo?
**Sin delay:**
- ❌ Batch processing consume 100% del I/O
- ❌ Queries de aplicación se vuelven lentas
- ❌ Lock escalation puede ocurrir
**Con delay de 1s:**
- ✅ Otras queries tienen ventana para ejecutar
- ✅ I/O distribuido
- ✅ Experiencia del usuario preservada
**Trade-off:** Migración toma +1s por batch (~25% más lenta), pero sistema permanece responsivo.
---
## Estado Actual & Próximos Pasos
### Estado Actual (Diciembre 2024)
📝 **Fase de Preparación:**
- ✅ Discovery completo (100M registros identificados)
- ✅ Scripts de migración desarrollados
- ✅ Pruebas en ambiente de homologación
- 🔄 Validación de performance
- ⏳ Esperando ventana de mantenimiento para producción
### Próximos Pasos
1. **Backup completo** de producción
2. **Ejecución en producción** (ambiente 24/7)
3. **Monitoreo en tiempo real** durante migración
4. **Validación post-migración** (integridad, performance)
5. **Documentación de lessons learned**
---
## Lecciones Aprendidas (Hasta Ahora)
### 1. Experiencia Anterior Vale Oro
Decisión de usar phased commits vino de **experiencia práctica** en proyectos anteriores, no de documentación o IA.
**Situaciones similares anteriores:**
- Migración de datos en e-commerce (50M registros)
- Conversión de encoding (UTF-8 en 100M+ rows)
- Particionamiento de tablas históricas
---
### 2. "Measure Twice, Cut Once"
Antes de ejecutar en producción:
- ✅ Pruebas exhaustivas en homologación
- ✅ Scripts validados y revisados
- ✅ Rollback probado
- ✅ Estimaciones de tiempo confirmadas
**Tiempo de preparación:** 3 semanas
**Tiempo de ejecución:** Estimado en 48 horas
**Ratio:** 10:1 (preparación vs ejecución)
---
### 3. Personalización > One-Size-Fits-All
El proceso CNPJ Fast original necesitó ser **reestructurado** para este cliente.
**Lección:** Los procesos deben ser:
- Estructurados lo suficiente para repetir
- Flexibles lo suficiente para adaptar
---
### 4. Monitoreo es Crucial
Scripts con **log detallado** de progreso permiten:
- Estimar tiempo restante
- Identificar cuellos de botella
- Pausar/reanudar con confianza
- Reportar estado a stakeholders
```sql
-- Log example
Processed: 10.000.000 rows. Batch: 100.000
Elapsed time: 3600 seconds (10% complete, ~9h remaining)
```
---
## Optimizaciones de Performance
### Optimizaciones Implementadas
1. **Índice temporal WHERE NULL**
- Acelera lookup de registros no migrados
- Removido tras conclusión
2. **Batch size optimizado**
- Balanceado entre performance y lock time
3. **Transaction log management**
```sql
-- Verificar crecimiento del log
DBCC SQLPERF(LOGSPACE);
-- Ajustar recovery model (si permitido)
ALTER DATABASE MyDatabase SET RECOVERY SIMPLE;
```
4. **Ejecución en horario de menor carga**
- Ventana de mantenimiento nocturna
- Fin de semana (si es posible)
---
**Resultado esperado:** Migración de 100 millones de registros en ~48 horas, sin downtime significativo y con posibilidad de rollback rápido.
[¿Necesita migrar volúmenes masivos de datos? Póngase en contacto](#contact)