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