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