Files
go-migrate/benchmark-results.md

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.