2.7 KiB
2.7 KiB
Benchmark go-migrate — 2,000,000 filas
Tabla: Cartografia.MANZANA
Fecha: 2026-05-29
Entorno: Docker local (MSSQL 2022 Developer / PostgreSQL 16 + PostGIS)
Resultado final — 5 pasadas cada dirección
| Métrica | MSSQL → PostgreSQL | PostgreSQL → MSSQL |
|---|---|---|
| Promedio | 8.37s | 16.77s |
| Mediana | 8.16s | 16.33s |
| Mínimo | 7.75s | 16.03s |
| Máximo | 9.17s | 18.46s |
| Desv. estándar | 0.56s | 1.01s |
| Throughput promedio | ~238,892 filas/seg | ~119,261 filas/seg |
| Factor | 1x | ~2x más lento |
Evolución del tuning PG → MSSQL
| Etapa | Config | Tiempo | Throughput | Δ |
|---|---|---|---|---|
| Corrida 1 — original | conservadora | 236.8s | ~8,446 /seg | baseline |
| Corrida 2 — igualada | mismos parámetros | 21.94s | ~91,148 /seg | +10.8x |
| Tuning A | 4ext/8load 50k | 17.37s | ~115,200 /seg | +1.27x |
| Tuning C | 16 loaders | 17.26s | ~115,900 /seg | +1.28x |
| Tuning D — óptimo | 8ext/8load 50k | ~16.77s | ~119,261 /seg | +1.37x |
| Tablock + 8 loaders | lock exclusivo serial | ~44s | ~45,000 /seg | ❌ regresión |
| Tablock + 1 loader | minimal logging | ~47s | ~42,000 /seg | ❌ regresión |
Configuración óptima — config-reverse.yaml
max_parallel_workers: 4
defaults:
batches_per_partition: 4
max_extractors: 8 # ← mayor lever de mejora
extractor_batch_size: 25000
extractor_queue_size: 32
max_transformers: 8
transformer_batch_size: 50000
transformer_queue_size: 32
max_loaders: 8
loader_batch_size: 50000 # sweet spot — 75k y 100k peores
Análisis de la brecha final (~2x)
La diferencia residual entre ambas direcciones es estructural y está en el protocolo de escritura:
| Protocolo | Mecanismo | Overhead |
|---|---|---|
pgx.CopyFrom (→ PG) |
PostgreSQL COPY protocol — streaming binario sin SQL | mínimo |
mssql.CopyIn (→ MSSQL) |
BCP protocol — row-by-row dentro de un bulk statement | mayor por fila |
mssql.CopyIn itera fila a fila via stmt.ExecContext(row...) antes del flush final, lo que introduce overhead por fila independientemente del batch size. pgx.CopyFrom hace streaming puro.
Hallazgos sobre Tablock
Tablock: true en mssql.BulkOptions resultó contraproducente en ambos escenarios:
- Con 8 loaders paralelos: cada loader compite por un lock exclusivo de tabla → serialización completa (~44s)
- Con 1 loader + batch enorme: sin contención de locks, pero overhead de log + gestión de la lock exclusiva superó el beneficio de minimal logging (~47s)
Conclusión: para este patrón de carga (múltiples loaders concurrentes), Tablock: false (default) es siempre mejor.