Construir PriceWatch significó aceptar una realidad incómoda: el scraping falla, seguido. Una tienda cambia su HTML, un proxy se cae, un timeout ocurre a mitad de una petición. La pregunta no es cómo evitar los fallos, sino cómo hacer que el sistema se recupere solo.
Por qué una cola y no un cron simple
Un cron que corre cada 10 minutos y hace scraping de forma secuencial se rompe apenas agregas la URL 41: el job anterior aún no termina cuando el siguiente debería empezar. Una cola con workers concurrentes desacopla "cuándo se agenda el trabajo" de "cuándo se ejecuta".
Backoff exponencial en la práctica
Con delay: 2000 y backoff exponencial, los reintentos quedan así:
| Intento | Espera antes de reintentar |
|---|---|
| 1 | inmediato |
| 2 | 2s |
| 3 | 4s |
| 4 | 8s |
| 5 | 16s |
Esto importa porque un fallo transitorio (un timeout de red, un rate-limit momentáneo) suele resolverse solo en segundos. Reintentar de inmediato en un loop ajustado solo empeora el rate-limit; esperar cada vez más le da tiempo al problema de desaparecer.
Diferenciar errores recuperables de errores permanentes
No todos los fallos deberían reintentarse. Si una tienda cambió su estructura HTML y el selector ya no existe, reintentar 5 veces solo retrasa lo inevitable.
En la práctica separé los errores en dos categorías desde el inicio: transitorios (reintentables, dejan que BullMQ haga su trabajo) y estructurales (se marcan para revisión manual y generan una alerta). Esa distinción evitó que el sistema reintentara indefinidamente jobs que nunca iban a tener éxito.
Worker completo
Así se ve el worker completo, uniendo colas, reintentos, backoff y la separación de errores: