Escalando geração de imagens: filas, idempotência e retries
Geração de imagens quebra as premissas sobre as quais a maioria das arquiteturas web é construída. As requisições levam dezenas de segundos em vez de milissegundos, a capacidade é genuinamente finita porque é física, e uma requisição que falha já custou dinheiro de verdade. Os padrões que mantêm isso correto são bem conhecidos — apenas não são os que um backend CRUD típico já tem.
A fila é o projeto
O primeiro instinto é chamar a API direto do handler da requisição. Funciona em desenvolvimento e falha na primeira vez que chega tráfego real, porque não há nada entre demanda e capacidade: um pico vira uma parede de timeouts, e não existe registro do que estava em andamento quando o processo reiniciou.
Ponha uma fila durável no meio. "Durável" é a palavra operativa — uma lista em memória perde todo job em andamento no deploy, e deploys acontecem durante o tráfego. Uma tabela no banco funciona perfeitamente bem nessa escala e, de quebra, dá um registro de estado recuperável.
Backpressure é um recurso
Com uma fila no lugar você pode decidir quantos jobs são permitidos em andamento ao mesmo tempo, e esse limite é uma decisão de produto, não um detalhe técnico. Alto demais e tudo fica lento para todos. Baixo demais e a capacidade fica ociosa enquanto a fila cresce. O que importa é que o limite exista e seja aplicado em um único lugar, para que um pico de tráfego alongue a fila em vez de degradar todas as requisições.
Idempotência e a falha ambígua
O modo de falha que custa dinheiro não é uma resposta de erro — erros são fáceis. É o caso ambíguo: a requisição saiu do seu processo, a conexão caiu e você não consegue dizer se o job foi criado. Se você repetir, pode ter pago duas vezes. Se não repetir, o usuário pode ter pago por nada.
Uma idempotency key resolve isso tornando o retry seguro: a mesma chave devolve o job original em vez de criar um segundo. As regras são simples e fáceis de errar — gere a chave quando o usuário age, não quando a chamada HTTP é feita; guarde-a com o registro do job; reutilize-a em cada retry daquela ação.
# a mesma chave para cada retry de uma ação do usuário 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
O que não repetir automaticamente
Existe uma categoria de falha em que o movimento seguro é parar. Se o seu processo morreu entre enviar uma submissão e registrar a resposta, você genuinamente não sabe o que aconteceu — e sem uma chave para deduplicar, um retry automático é uma moeda ao alto que pode cobrar duas vezes. Marque esses jobs como precisando de atenção em vez de repeti-los às cegas. Um job visivelmente travado é um desfecho melhor que uma cobrança duplicada silenciosa.
Reconciliação: o loop que te salva
Eventos ao vivo são convenientes e pouco confiáveis. Sockets caem, processos reiniciam, e a única mensagem que importava chega quando ninguém está escutando. A solução é um loop em segundo plano que pergunta periodicamente à API sobre cada job que o seu banco ainda considera ativo, e atualiza o estado com a resposta.
Esse único loop absorve uma classe inteira de bugs. Eventos perdidos deixam de importar. Um restart no meio da geração deixa de importar. Todo job converge para seu estado real dentro de um intervalo de reconciliação, aconteça o que acontecer com a conexão nesse meio-tempo.
Progresso sem martelar o seu banco
A geração emite progresso com frequência — potencialmente várias vezes por segundo por job. Gravar cada tique no banco multiplica sua carga de escrita sem nenhuma informação adicional. Limite a persistência a algo como uma vez por segundo e sempre grave o estado final; depois transmita os valores ao vivo para os clientes conectados sem tocar no armazenamento.
Um checklist que se sustenta
- Fila durável entre usuários e API, recuperável após restart.
- Um limite explícito de jobs em andamento, aplicado em um único lugar.
- Idempotency key gerada por ação do usuário, armazenada e reutilizada no retry.
- Falhas ambíguas de envio sinalizadas em vez de repetidas automaticamente.
- Um loop de reconciliação que converge o estado independentemente de eventos perdidos.
- Escritas de progresso limitadas, progresso ao vivo pelo socket.
Nada disso é específico de geração de imagens — é higiene comum de sistemas distribuídos. Só fica visível aqui porque cada unidade de trabalho é lenta e cara o bastante para que os atalhos de sempre deixem de ser invisíveis.
Perguntas frequentes
Preciso de um message broker?
Normalmente não nessa escala. Uma tabela no banco com uma query de claim dá durabilidade e um histórico de estado recuperável sem mais uma peça móvel.
Quantos jobs devo permitir em andamento?
Comece baixo, meça a espera na fila contra o tempo de conclusão e aumente até a latência parar de melhorar. O número importa menos do que existir um limite aplicado.
Jobs com falha devem tentar de novo automaticamente?
Erros claros, sim. Falhas ambíguas de envio, em que você não sabe se o trabalho foi criado, não — sinalize-as, porque um retry cego pode duplicar uma geração paga.
Construa com a uncloth.app
Um endpoint REST, onze presets, resultados entregues ao seu backend. Conte o que você está construindo e enviaremos os dados de acesso.
Solicitar acesso