Back to Blog
InfrastructureTechnical Note

Moving Image Generation Out of the Request Path

Image generation could keep running after its HTTP connection timed out. A stored job ID gave the browser a way to retrieve the result without submitting the prompt again.

April 18, 20223 min read
Async JobsImage GenerationQueuesFailure Recovery

The first image-generation endpoint waited for the model to finish before returning a response. When generation crossed the gateway limit, the browser received an error while the server kept computing.

Return an ID before running the model

I moved generation out of the request handler. The endpoint validated the prompt, created a pending job, and returned its ID. The worker generated the image, stored the file, and updated the record with its location.

The browser then asked about the job instead of holding the original request open. Closing the tab changed the connection, not the identity of the generation request.

prompt
→ validate
→ create job
→ worker generates image
→ store result
→ update job

Keep the result attached to the request

The table held a session ID, prompt hash, sanitized prompt, model version, status, timestamps, failure reason, and storage URL. That was enough to distinguish an unfinished job from a completed one and find the image after reconnecting.

SQL image-generation job table with a UUID, session ID, prompt hash, status, model version, timestamps, failure reason, and storage URL.
Image-generation job record

Mark the job completed after the upload succeeds, with the storage URL attached. Generation or storage failures need a failed state the browser can display.

Reconnecting and resubmitting are different

Reading an existing job does not run the model again. Submitting the same prompt twice can still create two jobs: the UUID identifies each row, and the prompt hash shown here has no unique constraint.

Duplicate submissions need a separate idempotency rule. The client can send a key for one submission, and the server can associate that key with the accepted job. Retrying the request then retrieves the existing ID rather than starting another generation.

Worker retries are another decision. The displayed schema has no attempt counter, so it cannot enforce a retry limit by itself. A worker also needs to claim a pending job before running it; a status column alone does not prevent two workers from picking up the same row.

For the browser, recovery starts with the ID it already has. It can check a pending or running job, retrieve a completed result, or show the failure. A new generation should be an explicit submission, not a side effect of reconnecting.

Have a similar project?

Talk to Me