BookinglyTech News
Infraestructura

Gestion de cuotas en IA: la diferencia clave entre timeout y fallo confirmado

RetroPrompt explica cómo su Worker de Cloudflare evita doble contabilidad al reservar slots de generación de imágenes, tratando los timeouts como estado pendiente.

2 min de lecturaDev.to0 vistas

Un timeout de red no es lo mismo que un fallo confirmado, y tratarlos igual corrupta la cuenta de cuotas diarias. En el proyecto RetroPrompt, un generador de retratos que corre sobre Cloudflare Workers y la base de datos D1, este matiz era la causa de un bug donde los usuarios perdían acceso a sus slots tras errores de red. La solución no está en la interfaz, sino en cómo se modela el estado de cada intento de generación en la base de datos.

Tres estados, una invariant

El sistema no usa un simple contador numérico que se incrementa y decrementa. Cada intento se registra con un identificador, la cuenta del usuario o un ID anónimo, la fecha UTC y un estado: pending, success o failed. La capacidad restante se calcula restando de la cuota diaria tanto los éxitos confirmados como las peticiones pendientes.

Aquí reside la primera trampa técnica: si la reserva de capacidad se hace con una consulta SELECT seguida de un INSERT incondicional, dos peticiones concurrentes pueden ver el mismo slot disponible y ambas insertar. En la implementación de SQLite (D1), la comprobación de límite y la inserción condicional se ejecutan en una sola sentencia SQL. Esto garantiza que, si el límite ya se ha alcanzado, la inserción no escribe ninguna fila y la aplicación puede detectar el rechazo antes de lanzar la llamada costosa al proveedor de IA.

El segundo problema era más sutil. El proveedor devuelve un ID de tarea asíncrona que el Worker debe vigilar. En una versión anterior, si la vigilancia detectaba un estado de fallo explícito del proveedor, el código lanzaba una excepción general. El manejador de excepciones la interpretaba como un timeout, dejando la petición en estado pending. Para el usuario, eso significaba que un slot se consumía aunque la imagen nunca se generara.

La corrección exige distinguir explícitamente el fallo terminal del proveedor del desconocimiento técnico. Si el proveedor confirma el fallo, la transición de pending a failed libera el slot. Si ocurre un timeout de red o se pierde la respuesta, el sistema conserva el estado pending hasta el cambio de día UTC. Es una decisión conservadora: protege el presupuesto de créditos de pago, aunque penaliza la experiencia de usuario al forzar una espera incluso cuando el error podría haber sido trivial.

No es un sistema de reembolsos ni garantiza ejecución exacta de una vez (exactly-once). Mejoras futuras podrían incluir la persistencia de IDs de tarea del proveedor para reconciliaciones asíncronas o el uso de claves de idempotencia, si el proveedor las soporta. Mientras tanto, las pruebas de regresión deben validar estas transiciones de estado con mocks, separando la lógica de contabilidad de la disponibilidad real del servicio externo.