uncloth.app Guías

Escalar la generación de imágenes: colas, idempotencia y reintentos

La generación de imágenes rompe las suposiciones sobre las que se construyen la mayoría de arquitecturas web. Las peticiones tardan decenas de segundos en lugar de milisegundos, la capacidad es realmente finita porque es física, y una petición fallida ya ha costado dinero real. Los patrones que mantienen esto correcto son bien conocidos; simplemente no son los que un backend CRUD típico ya tiene.

La cola es el diseño

El primer instinto es llamar a la API directamente desde el manejador de la petición. Funciona en desarrollo y falla la primera vez que llega tráfico real, porque no hay nada entre la demanda y la capacidad: un pico se convierte en un muro de timeouts y no queda registro de qué había en curso cuando el proceso se reinició.

Pon una cola durable en medio. «Durable» es la palabra clave: una lista en memoria pierde todos los trabajos en curso en cada despliegue, y los despliegues ocurren con tráfico. Una tabla en la base de datos funciona perfectamente a esta escala y te da, como efecto secundario, un registro recuperable del estado.

El backpressure es una función, no un estorbo

Con una cola en su sitio puedes decidir cuántos trabajos se permiten en curso a la vez, y ese límite es una decisión de producto más que un detalle técnico. Demasiado alto y todo va lento para todos. Demasiado bajo y la capacidad se queda ociosa mientras se acumula la cola. Lo importante es que el límite exista y se aplique en un único lugar, de modo que un pico de tráfico alargue la cola en vez de degradar todas las peticiones.

Idempotencia y el fallo ambiguo

El modo de fallo que cuesta dinero no es una respuesta de error: los errores son fáciles. Es el caso ambiguo: la petición salió de tu proceso, la conexión se cortó y no puedes saber si el trabajo llegó a crearse. Si lo reintentas, puede que hayas pagado dos veces. Si no lo reintentas, puede que un usuario haya pagado por nada.

Una clave de idempotencia resuelve esto haciendo seguro el reintento: la misma clave devuelve el trabajo original en lugar de crear un segundo. Las reglas son simples y fáciles de incumplir: genera la clave cuando el usuario actúa, no cuando se hace la llamada HTTP; guárdala con el registro del trabajo; reutilízala en cada reintento de esa acción.

# la misma clave para cada reintento de una acción del usuario
KEY=$(uuidgen)
curl -X POST https://api.example/api/v1/jobs \
  -H "X-API-Key: $API_KEY" -H "Idempotency-Key: $KEY" \
  -F image=@photo.jpg -F feature=undress -F consent=confirmed

Qué no reintentar automáticamente

Hay una categoría de fallo en la que lo seguro es detenerse. Si tu proceso murió entre enviar una petición y registrar la respuesta, realmente no sabes qué ocurrió, y sin una clave contra la que deduplicar, un reintento automático es una moneda al aire que puede facturar dos veces. Marca esos trabajos como pendientes de revisión en lugar de reintentarlos a ciegas. Un trabajo visiblemente atascado es mejor resultado que un cargo duplicado en silencio.

Reconciliación: el bucle que te salva

Los eventos en vivo son cómodos y poco fiables. Los sockets se caen, los procesos se reinician y el único mensaje que importaba llega cuando nadie escucha. La solución es un bucle en segundo plano que pregunta periódicamente a la API por cada trabajo que tu base de datos aún considera activo, y actualiza el estado con la respuesta.

Ese único bucle absorbe toda una clase de errores. Los eventos perdidos dejan de importar. Un reinicio a mitad de generación deja de importar. Cada trabajo converge a su estado real dentro de un intervalo de reconciliación, pase lo que pase con la conexión entre medias.

Progreso sin machacar la base de datos

La generación emite progreso con frecuencia, potencialmente varias veces por segundo y por trabajo. Escribir cada tick en tu base de datos multiplica la carga de escritura sin aportar información adicional. Limita la persistencia a algo así como una vez por segundo y escribe siempre el estado final, y luego difunde los valores en vivo a los clientes conectados sin tocar el almacenamiento.

Una checklist que se sostiene

  • Cola durable entre los usuarios y la API, recuperable tras un reinicio.
  • Un límite explícito de trabajos en curso, aplicado en un único lugar.
  • Clave de idempotencia generada por acción del usuario, almacenada y reutilizada en los reintentos.
  • Fallos de envío ambiguos marcados en lugar de reintentados automáticamente.
  • Un bucle de reconciliación que converge el estado aunque se pierdan eventos.
  • Escrituras de progreso limitadas y progreso en vivo por el socket.

Nada de esto es específico de la generación de imágenes: es higiene ordinaria de sistemas distribuidos. Simplemente se vuelve visible aquí, porque cada unidad de trabajo es lo bastante lenta y lo bastante cara como para que los atajos habituales dejen de pasar desapercibidos.

Preguntas frecuentes

¿Necesito un broker de mensajes?

Normalmente no a esta escala. Una tabla en la base de datos con una consulta de reclamación te da durabilidad y un registro recuperable del estado sin otra pieza móvil.

¿Cuántos trabajos debería permitir en curso?

Empieza bajo, mide la espera en cola frente al tiempo de finalización y súbelo hasta que la latencia deje de mejorar. El número importa menos que tener un único límite aplicado.

¿Los trabajos fallidos deberían reintentarse automáticamente?

Los errores claros, sí. Los fallos de envío ambiguos en los que no puedes saber si el trabajo se creó, no: márcalos, porque un reintento a ciegas puede duplicar una generación ya pagada.

Desarrolla sobre uncloth.app

Un solo endpoint REST, once presets y los resultados devueltos a tu backend. Cuéntanos qué estás construyendo y te enviamos los datos de acceso.

Solicitar acceso

Todas las guías