No ecossistema de arquiteturas orientadas a eventos no Azure, o Azure Service Bus é uma peça fundamental para garantir a comunicação desacoplada e resiliente. Entretanto, um padrão de implementação muito comum — o processamento de batches sem granularidade — costuma gerar desafios técnicos que impactam diretamente a performance e a saúde financeira das operações em cloud.
O problema: A falha "all-or-nothing"
Ao utilizar o Azure Functions com o gatilho (trigger) de Service Bus em modo batch, a função recebe um conjunto de mensagens para otimizar o throughput. O grande gargalo ocorre quando um subconjunto dessas mensagens falha. No modelo padrão, a falha em um único item resulta no rollback de todo o batch. Todos os eventos são retornados à fila para reprocessamento, incluindo aqueles que foram concluídos com sucesso.

Isso desencadeia uma série de efeitos colaterais conhecidos por engenheiros que operam sistemas distribuídos:
- Processamento duplicado: Mensagens reinjetadas desnecessariamente sobrecarregam os sistemas de jusante (downstream).
- Desperdício de compute: Custos adicionais de execução de função e consumo de I/O em tarefas que já deveriam estar encerradas.
- Ciclos de retry infinitos: Uma mensagem "poison" pode bloquear o fluxo, forçando o reprocessamento contínuo de itens saudáveis.
- Aumento da complexidade de idempotência: As aplicações consumidoras precisam implementar lógica adicional complexa para lidar com a natureza não determinística dessas reentregas.
A solução: Per-message settlement
Para mitigar esses riscos operacionais, o Azure Functions oferece a capacidade de liquidação por mensagem. Em vez de tratar o batch como um bloco monolítico, é possível realizar a resolução (settlement) de cada evento individualmente. Dependendo do resultado do processamento, você pode definir a ação específica para cada item:
- Complete: Finaliza a mensagem (sucesso).
- Abandon: Devolve a mensagem à fila para um novo processamento, ideal para erros transitórios.
- Dead-letter: Direciona mensagens malformadas para a fila de mensagens mortas, isolando o erro.
- Defer: Retém a mensagem para processamento agendado, útil quando há dependência de ordem.
Impacto estratégico para empresas brasileiras
A adoção de per-message settlement transcende a escrita de código; é uma decisão de eficiência operacional e FinOps. Ao evitar o reprocessamento redundante, reduzimos drasticamente o consumo de instâncias de computação e chamadas de API, impactando positivamente a fatura mensal do Azure.
Além disso, do ponto de vista de SecOps e resiliência, a detecção isolada de mensagens poison evita que falhas em fluxos de menor prioridade ou corrompidos afetem a entrega de pacotes de dados críticos para o negócio, mantendo o SLA e a estabilidade da plataforma em ambientes de escala massiva.
Implementação prática
Para habilitar este controle, é necessário desativar o autoCompleteMessages e configurar o cardinality como many. Segue exemplo em .NET (C# Isolated Worker):
public async Task ProcessOrderBatch(
[ServiceBusTrigger("orders-queue", Connection = "ServiceBusConnection")]
ServiceBusReceivedMessage[] messages,
ServiceBusMessageActions messageActions)
{
foreach (var message in messages)
{
try
{
var order = message.Body.ToObjectFromJson<Order>();
await ProcessOrder(order);
await messageActions.CompleteMessageAsync(message);
}
catch (Exception ex)
{
logger.LogError(ex, "Failed {MessageId}", message.MessageId);
await messageActions.DeadLetterMessageAsync(message);
}
}
}
Ao utilizar a injeção do ServiceBusMessageActions, o time de engenharia ganha o controle necessário para implementar políticas de exponential backoff sem a necessidade de infraestrutura adicional (como filas de retry externas), simplificando a arquitetura global e reduzindo a superfície de erro.
Artigo originalmente publicado em Azure Updates - Latest from Azure Charts.