1. Assuma que uma mensagem pode voltar#
Um worker conclui uma escrita e perde a conexão antes de confirmar a mensagem. A fila não tem como saber se o efeito aconteceu e entrega de novo. Esse caso basta para quebrar um consumidor que incrementa um contador sem identificar o evento. As filas Standard do SQS documentam entrega pelo menos uma vez; o consumidor precisa tolerar duplicatas.
Pense em idempotência como uma propriedade do efeito: repetir a mesma intenção deixa o estado de negócio igual ao de uma única execução. Isso não equivale a ignorar qualquer requisição parecida. Você precisa de uma identidade estável para a mesma operação e de uma política para a mesma chave com conteúdo diferente.
Para se aprofundar: Como escolher fila de mensagens: SQS, Cloudflare Queues ou QStash
Documentação: AWS: entrega pelo menos uma vez no SQS ↗
2. Atribua identidade antes de publicar#
O produtor precisa gerar e guardar o event_id junto com a operação de negócio. Repetir a publicação mantém esse identificador. O consumidor usa uma chave que inclua o nome dele ou a versão do efeito e o tenant verificado do evento; assim, dois consumidores diferentes podem processar legitimamente o mesmo evento.
Autentique o canal e valide o schema antes de confiar nesses campos. Guarde uma impressão digital (hash) do conteúdo relevante para detectar reutilização incorreta de chave. Decida por quanto tempo o registro de deduplicação vive de acordo com a janela real de retries, replays e retenção; apagá-lo cedo demais permite que um replay aplique o efeito de novo.
3. Amarre deduplicação e efeito local#
Quando o efeito está no mesmo banco de dados, uma constraint única e uma transação permitem amarrar a marca de processamento à escrita. Inserir a marca, fazer commit e só depois aplicar o efeito abre uma janela em que uma queda perde trabalho. Fazer primeiro o efeito fora da transação abre a janela inversa: duplicá-lo.
CREATE TABLE processed_events (
tenant_id uuid NOT NULL,
consumer text NOT NULL,
event_id uuid NOT NULL,
processed_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (tenant_id, consumer, event_id)
);begin transaction
insert dedup key on conflict do nothing returning event_id
if inserted:
apply business change in this same database transaction
else:
verify stored payload fingerprint matches this event
commit
ack messageO ON CONFLICT oferece o mecanismo para resolver uma colisão de unicidade. O seu código ainda precisa distinguir uma colisão válida de uma mensagem inconsistente e verificar se a pré-condição de negócio foi cumprida. Uma escrita que não afetou a linha esperada pode exigir erro e rollback, não sucesso silencioso.
Documentação: PostgreSQL: INSERT e ON CONFLICT ↗
4. Separe publicação e efeitos externos#
A transação anterior não inclui uma API externa. Se você envia um e-mail ou chama outro serviço antes do commit, um rollback local não desfaz esse efeito. Se fizer depois, uma queda pode impedir o envio. Projete esse limite com uma outbox transacional e, quando existir, uma chave de idempotência aceita pelo destino.
A outbox guarda a intenção de publicar junto com a mudança de negócio na mesma transação. Um relay a envia e registra o progresso. Esse relay também pode reenviar se cair entre publicar e registrar o sucesso; o consumidor continua precisando de deduplicação. A outbox resolve a perda entre banco e broker, mas não transforma todos os sistemas em uma transação única.
Documentação: AWS: padrão transactional outbox ↗
5. Faça retries com orçamento e dispersão#
Classifique as falhas: um timeout ou uma indisponibilidade temporária podem se resolver; um schema inválido não vai melhorar com repetição. Defina número de tentativas, tempo total e atraso máximo. Adicione jitter para que muitos workers não voltem a bater num serviço no mesmo instante. Respeite as indicações de espera do destino quando houver.
function retryDelayMs(attempt: number) {
const base = 500;
const cap = 30_000;
const ceiling = Math.min(cap, base * 2 ** Math.min(attempt, 16));
return Math.floor(Math.random() * ceiling);
}Ajuste a visibilidade ou o mecanismo de lease ao tempo de processamento e estenda-o se o provedor permitir. Se expirar enquanto você trabalha, outro consumidor pode receber a mesma mensagem. Não use um atraso longo como substituto da idempotência.
6. Projete uma DLQ que você consiga operar#
Uma fila de mensagens com falha precisa de dono, alerta e procedimento de revisão. Guarde causa, tentativas e versão do consumidor sem despejar segredos nos logs. Reproduza a falha no ambiente autorizado, corrija a causa e reenvie um lote pequeno com a identidade original antes de uma recuperação em massa.
| Ponto de queda | Resultado esperado |
|---|---|
| Antes do commit | Efeito e marca são revertidos; um retry pode processar. |
| Depois do commit, antes do ack | O retry detecta a chave e não repete o efeito. |
| Payload inválido | É isolado com uma causa acionável, sem loop infinito. |
| Destino externo ambíguo | Consulta-se ou reutiliza-se a identidade do destino; não se inventa sucesso. |
Meça a idade da mensagem mais antiga, erros, backlog e tempo até a recuperação. O preço por milhão ajuda a estimar o gasto, mas não diz quanto tempo o seu time vai levar para consertar um evento travado. Essa capacidade operacional faz parte da escolha de uma fila.
Fontes e escopo
Documentação consultada em 25 de setembro de 2026. Os exemplos e critérios de decisão são propostas editoriais; adapte-os ao contrato da sua aplicação e valide-os no seu ambiente de testes autorizado.
Compare filas de mensagens
Confira preços, limites, condições e fontes de cada opção.
Abrir comparador