Queues/Workers/async agents
Uma requisição que demora minutos não precisa manter a conexão HTTP aberta. Ela pode criar um job persistente, devolver um identificador e deixar um worker executar o trabalho. Essa separação introduz novas perguntas: o que acontece se o worker cai depois de produzir o efeito? Quem repete um job? Como o usuário distingue pendente de falha definitiva?
Nesta aula você implementará um ciclo de jobs com estados, idempotência, retries e dead-letter handling. Recuperaremos a stack da semana 42 e a separação entre geração e efeito da semana 41. O objetivo de domínio é sobreviver a reinício, repetir trabalho com segurança e não perder estado crítico. Uma fila em memória ajuda a entender o fluxo, mas não satisfaz esse objetivo sozinha.
PythonAgentesInfraestruturaAo terminar esta aula
- Fila, estado de job e efeito são contratos diferentes.
- Idempotência precisa sobreviver a reinício.
- Retries têm causa, espera, orçamento e limite.
Antes de continuar: Laboratório: Docker/Postgres/pgvector/Redis
Queues, workers e o contrato de um async job
InfraestruturaUma fila desacopla quem pede trabalho de quem o executa. O produtor cria uma mensagem ou job, e o worker processa quando há capacidade. Um fluxo assíncrono pode retornar 202 com jobId e um endpoint de status, mas esse retorno só deve ocorrer após a aceitação durável conforme o contrato. “Recebido no processo” e “registrado para recuperação” são eventos diferentes. Defina estados como pending, running, succeeded, retry_wait e dead, além de quem pode consultar o job.
Um background agent pode executar múltiplas etapas, chamadas de modelo e ferramentas dentro desse ciclo. Ele precisa de checkpoint e de limites de tempo, orçamento e autorização iguais aos do fluxo síncrono. Não persista credenciais dentro da mensagem para facilitar a retomada. Guarde referências e obtenha autorização no executor. O endpoint de status deve informar resultado ou erro sanitizado, sem revelar jobs de outra identidade. A fila não resolve isolamento de tenant nem qualidade do agente.
Entrega repetida e idempotência de efeito
FundamentosMuitos desenhos de fila aceitam redelivery: se o consumidor não confirma processamento, a mensagem pode voltar. Isso melhora recuperação, mas permite execução repetida. Idempotência significa que repetir a mesma operação lógica preserva o efeito esperado, não que o código nunca roda duas vezes. Para criar ticket, use chave de operação única e guarde o resultado. A mesma chave com argumentos diferentes deve ser conflito.
O ponto difícil é a janela entre efeito e confirmação. Se o worker cria o ticket e cai antes do ack, o próximo worker precisa descobrir o ticket existente. Quando efeito e estado do job estão no mesmo banco, uma transação pode aproximar esses eventos de forma controlada. Quando o efeito está em uma API externa, use suporte de idempotência do destino, ledger e reconciliação. Não prometa exactly once apenas por ativar ack tardio ou usar uma chave em memória: reinício e concorrência precisam de testes próprios.
Retries com classificação, espera e limite
FundamentosRetry faz sentido para falhas temporárias, como indisponibilidade transitória. Falhas permanentes, como argumento inválido ou acesso negado, devem ser encerradas ou encaminhadas para correção. Registre attempt, nextAttemptAt e categoria da falha. Exponential backoff aumenta o intervalo entre tentativas; jitter evita que muitos workers retomem juntos. A política deve respeitar deadline do job e orçamento restante, não apenas contar até um número fixo.
Uma tentativa é trabalho consumido e precisa aparecer no custo e no trace. Se o job exige consulta de política atual, decida se a retomada usa a versão original ou uma nova; mudar silenciosamente pode produzir resultado inconsistente. Uma tarefa que já realizou parte dos efeitos precisa de compensação ou checkpoints, em vez de reiniciar todo o fluxo. O teste deve simular falha depois de cada fronteira relevante e avaliar o estado recuperado, não somente a exceção retornada.
Dead-letter patterns e recuperação supervisionada
FundamentosUma dead-letter queue ou estado dead recebe trabalho que não pode continuar pela política definida. Isso evita retries infinitos, mas não resolve a causa nem conclui a tarefa. Guarde motivo, tentativas, versão, origem e um identificador para revisão. Dados sensíveis não devem aparecer indiscriminadamente no erro. Defina retenção e autorização de inspeção, porque mensagens problemáticas ainda carregam conteúdo do usuário.
Redrive devolve um item para processamento depois de corrigir a causa. Preserve sua operação lógica e a proteção de idempotência; criar uma chave nova sem necessidade pode duplicar um efeito anterior. Não reenvie em massa antes de corrigir a dependência ou os dados, pois isso recria a falha e a carga. Um relatório operacional inclui quantidade em dead, idade, motivo e resultado da recuperação. Mensagem enterrada sem alerta é trabalho perdido do ponto de vista do produto, mesmo que continue armazenada.
Redis Streams e frameworks de worker
RedisInfraestruturaRedis Streams oferece registro de entradas e mecanismos de grupos de consumidores. Consumir, confirmar e recuperar mensagens pendentes exige um protocolo coerente com a versão e a configuração adotadas. Celery fornece abstrações de tarefas, retries e workers sobre brokers suportados. A escolha depende do ecossistema e da operação que você consegue sustentar, mas ambas exigem decisões sobre durabilidade, confirmação, efeitos e reprocessamento.
A fila é transporte, e o banco de jobs pode ser a fonte de estado do produto. Se publicar uma mensagem e gravar o job forem operações separadas, uma falha entre elas pode gerar job sem mensagem ou mensagem sem job. O padrão outbox registra a intenção de publicação junto ao estado em uma transação, e um publicador envia depois com deduplicação. O exemplo local usa um banco único para explicar atomicidade; uma implantação com PostgreSQL e Redis deve testar essa fronteira adicional.
Job durável e efeito único no mesmo banco
FundamentosO exemplo usa sqlite3, biblioteca padrão do Python, para tornar a persistência reproduzível sem broker externo. Ele não afirma implementar Redis ou Celery. Uma transação registra o ticket e o estado final do job no mesmo banco. Jobs inválidos entram em dead, e um job já concluído pode ser chamado novamente sem criar outro ticket.
import sqlite3
db=sqlite3.connect('jobs-lab.sqlite')
db.executescript('''
CREATE TABLE IF NOT EXISTS jobs(id TEXT PRIMARY KEY,state TEXT,attempt INTEGER);
CREATE TABLE IF NOT EXISTS tickets(job_id TEXT PRIMARY KEY,subject TEXT);
''')
with db:
db.execute("INSERT OR IGNORE INTO jobs VALUES ('j1','pending',0)")
db.execute("INSERT OR IGNORE INTO jobs VALUES ('j2','pending',0)")
def work(job_id, subject):
with db:
row=db.execute('SELECT state FROM jobs WHERE id=?',(job_id,)).fetchone()
if not row: raise ValueError('unknown-job')
if row[0]=='succeeded': return 'already-done'
if row[0]=='dead': return 'needs-review'
db.execute('UPDATE jobs SET attempt=attempt+1 WHERE id=?',(job_id,))
if not subject:
db.execute("UPDATE jobs SET state='dead' WHERE id=?",(job_id,))
return 'invalid-input'
db.execute('INSERT OR IGNORE INTO tickets VALUES (?,?)',(job_id,subject))
db.execute("UPDATE jobs SET state='succeeded' WHERE id=?",(job_id,))
return 'done'
work('j1','consulta');work('j1','consulta');work('j2','')
assert db.execute("SELECT count(*) FROM tickets WHERE job_id='j1'").fetchone()[0]==1
assert db.execute("SELECT state FROM jobs WHERE id='j2'").fetchone()[0]=='dead'
print(db.execute('SELECT * FROM jobs ORDER BY id').fetchall())
db.close()A saída identifica j1 concluído e j2 em dead. Execute novamente: a unicidade de job_id preserva um ticket para j1. O exemplo não trata alteração de subject com a mesma chave; acrescente assinatura como na semana 41 antes de generalizá-lo. Também não possui lease para múltiplos workers, retry temporal ou efeito externo. Essas limitações são justamente as próximas fronteiras a testar.
Exercício aplicado
O worker cria um ticket e cai antes de confirmar a mensagem. Outro worker recebe o mesmo job. Explique a recuperação segura e por que ack tardio não basta.
- Busque o estado e o resultado por chave durável.
- Verifique unicidade e assinatura da operação.
- Recupere o ticket existente em vez de recriá-lo.
- Confirme a mensagem após concluir o protocolo.
Abrir resolução comentada
Redelivery é esperado em desenhos que priorizam recuperação. A chave de operação e o resultado durável permitem reconhecer o efeito anterior. A confirmação da fila informa transporte; ela não desfaz nem deduplica o ticket automaticamente.
No exemplo, efeito e estado estão no mesmo banco e podem participar de uma transação. Para API externa, é necessário um protocolo de idempotência e reconciliação com o destino. Retry e dead-letter também precisam preservar a identidade lógica do trabalho.
import sqlite3
db=sqlite3.connect('jobs-lab.sqlite')
db.executescript('''
CREATE TABLE IF NOT EXISTS jobs(id TEXT PRIMARY KEY,state TEXT,attempt INTEGER);
CREATE TABLE IF NOT EXISTS tickets(job_id TEXT PRIMARY KEY,subject TEXT);
''')
with db:
db.execute("INSERT OR IGNORE INTO jobs VALUES ('j1','pending',0)")
db.execute("INSERT OR IGNORE INTO jobs VALUES ('j2','pending',0)")
def work(job_id, subject):
with db:
row=db.execute('SELECT state FROM jobs WHERE id=?',(job_id,)).fetchone()
if not row: raise ValueError('unknown-job')
if row[0]=='succeeded': return 'already-done'
if row[0]=='dead': return 'needs-review'
db.execute('UPDATE jobs SET attempt=attempt+1 WHERE id=?',(job_id,))
if not subject:
db.execute("UPDATE jobs SET state='dead' WHERE id=?",(job_id,))
return 'invalid-input'
db.execute('INSERT OR IGNORE INTO tickets VALUES (?,?)',(job_id,subject))
db.execute("UPDATE jobs SET state='succeeded' WHERE id=?",(job_id,))
return 'done'
work('j1','consulta');work('j1','consulta');work('j2','')
assert db.execute("SELECT count(*) FROM tickets WHERE job_id='j1'").fetchone()[0]==1
assert db.execute("SELECT state FROM jobs WHERE id='j2'").fetchone()[0]=='dead'
print(db.execute('SELECT * FROM jobs ORDER BY id').fetchall())
db.close()Como conferir seu resultado
- Reexecução após reinício mantém um ticket.
- Falha dentro da transação não deixa efeito parcial.
- Erro permanente não entra em retry infinito.
- Entrada pendente é inspecionada e confirmada no Redis.
Aplique em um problema novo
Primeiro resolva sem consultar a resposta. Explique suas decisões e guarde a evidência. A conclusão de leitura é independente desta autoavaliação.
Confira seus pré-requisitos
- Distinguir ack e efeito do job.
- Usar identidade/ledger duráveis.
Worker cria nota e cai antes de ack. Outro worker recebe job K2. Como impedir duplicação sem perder trabalho?
Conferir raciocínio e critérios de domínio
Entrega repetida é possível; ack não deduplica efeito.
Intenção K2 precisa registro/efeito com unicidade e transação apropriada no banco.
Retry consulta resultado; erro permanente vai a DLQ com motivo e revisão.
Evidências para autoavaliação ou revisão por pares
- Entrega: Entrega repetida é possível; ack não deduplica efeito.
- Atomicidade: Intenção K2 precisa registro/efeito com unicidade e transação apropriada no banco.
- Recuperação: Retry consulta resultado; erro permanente vai a DLQ com motivo e revisão.
Um erro frequente
Ack tardio garante exatamente uma nota.
Ack controla entrega; idempotência/atomicidade protegem o efeito.
Teste sua compreensão
Responda com suas palavras antes de abrir o comentário. Saber explicar uma decisão é parte do domínio.
1. Redelivery implica necessariamente efeito duplicado?
2. Dead-letter conclui a tarefa do usuário?
Não.
Ela separa trabalho problemático para revisão e recuperação.
3. Por que um outbox é útil?
Para registrar estado e intenção de publicação juntos.
Publicação independente do commit cria janelas de perda ou inconsistência.
Seu progresso fica salvo neste navegador. Concluir a leitura não substitui demonstrar o domínio nos exercícios.
Referências e aprofundamento
Documentação oficial e trabalhos originais. As referências registram o escopo e as limitações para você conferir o que sustentam.
- Redis streaming
Redis • consulta: 2026-10-06
RedisAPIsStreams, logs append-only, consumer groups, acknowledgments e distribuição entre workers.
Limites: Entrega at-least-once requer tratamento de duplicações; Redis Streams e uma fila de jobs têm semânticas diferentes.
- Tasks
Celery • consulta: 2026-10-06
FundamentosTasks, workers, execução assíncrona, idempotência, acknowledgment, retries e limites de tempo.
Limites: Retry e redelivery podem repetir efeitos; desenhar tarefa idempotente e testar interrupções.
- Asynchronous Request-Reply Pattern
Microsoft • consulta: 2026-10-06
FundamentosSeparar aceite de requisição, processamento longo e consulta de status.
Limites: Persistir status e tratar timeout/cancelamento; assíncrono não elimina falhas.
- Retry pattern
Microsoft • consulta: 2026-10-06
FundamentosRetries de falhas transitórias, política de atrasos, logging e impacto em transações.
Limites: Retries de operações não idempotentes e camadas aninhadas podem duplicar efeitos e ampliar carga.
- Using dead-letter queues in Amazon SQS
AWS • consulta: 2026-10-06
InfraestruturaDLQ, política de redrive, máximo de recebimentos e retenção.
Limites: DLQ exige inspeção e política de reprocessamento; seu uso pode interferir em ordenação estrita FIFO.