El cuello de botella de Node.js en validación de emails
La validación de emails a escala no es trivial. Para cada dirección hay que hacer al menos tres verificaciones: sintaxis, existencia del dominio vía DNS/MX, y verificabilidad del buzón vía SMTP.
El motor inicial estaba en Node.js con async/await y Promise.all. Funcionaba bien con listas pequeñas, pero cuando subíamos a miles de emails, el event loop de Node mostraba latencias inconsistentes. Con 5000 emails en paralelo, Node.js abría demasiadas conexiones SMTP simultáneas y los servidores remotos las rechazaban con errores 421 (too many connections).
Por qué Go
Go tenía goroutines como primitiva de concurrencia nativa, channels para comunicación entre goroutines, garbage collector eficiente, compilación a binario estático, y una librería estándar completa para HTTP y DNS.
El modelo de concurrencia de Go
La solución en Go usa un worker pool con un semáforo para controlar la concurrencia máxima. Lo que Node.js hacía con Promise.all —todo al mismo tiempo, sin límite— Go lo hace con goroutines y semáforos: exactamente el nivel de concurrencia que querés.
El pivot de SSE por Caddy
El plan original era hacer streaming de resultados via SSE desde el servidor Go. El problema: Caddy bufferea las respuestas HTTP antes de enviarlas al cliente, destruyendo la experiencia de streaming.
Elegimos cambiar la arquitectura del frontend: el backend Go procesa la lista completa y devuelve los resultados en lotes, mientras el frontend simula el efecto de streaming con animaciones client-side usando setTimeout. El resultado final es visualmente indistinguible del SSE real.
Performance real en producción
Después de la migración a Go, el throughput con 200 goroutines concurrentes procesando listas de 10.000 emails bajó el tiempo total de minutos a segundos. La verificación MX + SMTP por dominio quedó en menos de 2 segundos en el percentil 95.
