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