1. Separar aceitação, processamento e confirmação
Uma instrução pode ter sido aceite pelo broker mesmo que o produtor não receba a resposta. Também pode ter produzido o efeito de negócio antes de o consumidor conseguir confirmar a mensagem. Estes intervalos fazem parte do desenho. Em PeekLock, uma mensagem não concluída pode voltar a ser entregue; em ReceiveAndDelete, uma falha depois da entrega pode deixar trabalho perdido. Num fluxo fictício de reconciliação, regista separadamente o identificador da instrução, a tentativa de envio, a receção, o commit do resultado e o settlement. Um timeout de rede não prova que a operação anterior falhou. A equipa de suporte precisa de consultar o estado de negócio antes de decidir repetir uma instrução. O requisito deve descrever o resultado aceitável perante falhas em cada fronteira.
2. Distinguir deduplicação do broker e idempotência
Duplicate detection usa a identidade da mensagem dentro de uma janela configurada; uma repetição com identidade correspondente pode ser aceite no envio e descartada pelo broker. Por isso, um novo identificador em cada retry pode anular a proteção pretendida. Esta função não transforma o commit de uma base externa e o settlement numa única transação. O consumidor continua a precisar de um contrato idempotente. No modelo local, o identificador processado e a alteração de negócio são gravados na mesma transação SQLite em memória. Uma falha antes do commit reverte ambos; uma repetição após commit reconhece o identificador e não aplica outra alteração. O exercício demonstra apenas esse limite local. Para um efeito externo, como chamar outra API, é necessário um contrato adicional que o modelo não fornece.
3. Escolher a unidade de ordenação
Sessions permitem agrupar mensagens relacionadas para processamento ordenado, com um recetor a deter o lock da sessão. Escolhe SessionId a partir da unidade que precisa de ordem, como uma instrução composta ou uma conta fictícia, e não apenas para reduzir configuração. Sessões diferentes podem avançar em paralelo; uma única sessão para todo o negócio pode limitar o débito. Dentro do consumidor, mantém a ordem dos efeitos que dependem uns dos outros, evitando lançar tarefas concorrentes sem coordenação. O número de sequência, por si só, não garante a ordem de conclusão. Também não assumes que reenviar uma mensagem retirada da DLQ a coloca na posição histórica original. Define como reconciliar uma etapa atrasada quando outras já produziram resultados.
4. Dimensionar o trabalho em voo face aos locks
Prefetch pode reduzir espera pela rede, mas em PeekLock o relógio do lock começa quando a mensagem entra no buffer, antes de o handler a processar. Um buffer muito grande com processamento lento pode consumir a validade do lock em espera. Mede distribuição de tempos, idade no buffer e falhas de settlement antes de aumentar concorrência ou renovar locks indiscriminadamente. Numa carga fictícia, os handlers executam em dois segundos, mas algumas mensagens esperam mais de um minuto no buffer; otimizar apenas o código do handler não explica toda a falha. Reduzir o trabalho antecipado e ajustar capacidade pode ser mais adequado. Verifica ainda se o SDK escolhido suporta prefetch. Um desenho válido num cliente .NET não deve ser transcrito para outro SDK sem confirmar as capacidades disponíveis.
5. Operar a DLQ como trabalho pendente
A dead-letter queue permite separar mensagens que não conseguiram seguir o processamento normal. Não é um arquivo que se limpa sozinho: define um owner, critérios de triagem e um procedimento para completar ou reprocessar cada mensagem. Classifica a causa com contexto suficiente, evitando colocar dados sensíveis em descrições de erro. Uma falha de esquema exige corrigir o contrato ou transformar a mensagem com aprovação; uma indisponibilidade transitória exige confirmar recuperação da dependência. Reenviar tudo sem essa análise pode repetir a mesma falha ou duplicar efeitos. Num topic, considera as subscrições afetadas e os seus consumidores. O indicador operacional deve relacionar a idade das mensagens e os resultados de negócio pendentes, não apenas o número total acumulado na DLQ.
6. Fechar o contrato entre dados e publicação
Quando a aplicação grava dados e publica uma mensagem em operações separadas, existe uma janela em que só uma delas pode ter concluído. O padrão outbox guarda a intenção de publicação com a alteração de negócio dentro da mesma fronteira transacional e usa um processo posterior para entregar a mensagem. A entrega pode repetir-se, pelo que o consumidor continua a precisar de idempotência. Para Cosmos DB, respeita o âmbito transacional da partição em vez de assumir atomicidade entre quaisquer documentos. No plano de transição, inclui biblioteca e protocolo suportados. A documentação Microsoft indica 30 de setembro de 2026 como data de retirada do suporte das bibliotecas Service Bus antigas e do protocolo SBMP; os exemplos novos devem usar clientes suportados. O exercício local não depende desses clientes nem prova conectividade com o serviço.
import sqlite3
db = sqlite3.connect(":memory:")
db.executescript("CREATE TABLE done(id TEXT PRIMARY KEY); CREATE TABLE balance(n INTEGER); INSERT INTO balance VALUES(100);")
def apply(message_id, delta, fail=False):
with db:
inserted = db.execute("INSERT OR IGNORE INTO done VALUES(?)", (message_id,)).rowcount
if not inserted:
return "duplicate"
db.execute("UPDATE balance SET n=n+?", (delta,))
if fail:
raise RuntimeError("fictional failure before commit")
return "applied"
assert apply("instruction-A", 5) == "applied"
assert apply("instruction-A", 5) == "duplicate"
assert db.execute("SELECT n FROM balance").fetchone()[0] == 105
try:
apply("instruction-B", 3, fail=True)
except RuntimeError:
pass
assert db.execute("SELECT COUNT(*) FROM done WHERE id='instruction-B'").fetchone()[0] == 0
assert db.execute("SELECT n FROM balance").fetchone()[0] == 105
assert apply("instruction-B", 3) == "applied"
assert db.execute("SELECT n FROM balance").fetchone()[0] == 108
db.close()
print("seven local transaction checks passed; no Service Bus request performed")
Caso fictício: o consumidor confirma a alteração de posição, perde a ligação antes de Complete e recebe novamente a mensagem. O identificador já registado impede repetir a alteração; o operador confirma o resultado e acompanha o settlement.
Armadilhas comuns
Usar um MessageId novo em cada retry; assumir que deduplicação torna uma base externa transacional com o broker; esquecer tempo no buffer; reenviar DLQ sem reconciliar efeitos.
Tópicos relacionados: Transactional outbox · Concorrência em Cosmos DB · Observabilidade de integrações
O contrato de negócio precisa de sobreviver a repetições, interrupções e reordenação durante a recuperação.
Referência: Prevent Service Bus message loss and duplicates · AZ-305 objectives 2026-04-17