Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Exactly-Once no Node.js: o que realmente significa

Atualizado em: 16 de outubro de 2026

Rack de servidores processando fluxos de dados no Node.js

Exactly-Once no Node.js é uma garantia frequentemente desejada em sistemas distribuídos: cada mensagem produziria seu efeito uma única vez. Na prática, redes falham, acknowledgements se perdem e processos caem entre o efeito e a confirmação.

Por isso, sistemas confiáveis normalmente combinam entrega pelo menos uma vez com processamento idempotente. Algumas plataformas oferecem transações em limites específicos, mas nenhum recurso elimina a necessidade de definir claramente qual efeito deve ser único.

Entrega e processamento

“Mensagem entregue uma vez” e “efeito aplicado uma vez” são conceitos diferentes. O broker pode reenviar uma mensagem, enquanto a aplicação impede a duplicação no banco.

A janela de falha

await cobrarCartao(event);
await confirmarMensagem(event);

Se o processo cair depois da cobrança e antes da confirmação, o broker reenviará. Sem uma idempotency key, pode ocorrer cobrança duplicada.

At-most-once

Na entrega no máximo uma vez, a mensagem pode ser perdida, mas não é repetida. É aceitável para métricas aproximadas, porém inadequado para operações financeiras ou estoque.

At-least-once

Na entrega pelo menos uma vez, a mensagem não deve ser perdida, mas pode chegar novamente. Esse modelo é comum e exige idempotência.

Exactly-once dentro de um banco

Quando o registro da mensagem e o efeito usam o mesmo banco, uma transação local pode garantir aplicação única.

await db.transaction(async trx => {
  const inserted = await trx.query(
    `INSERT INTO inbox (message_id, processed_at)
     VALUES ($1, now())
     ON CONFLICT DO NOTHING
     RETURNING message_id`,
    [event.id]
  );

  if (inserted.rowCount === 0) return;

  await trx.query(
    'UPDATE contas SET saldo = saldo + $1 WHERE id = $2',
    [event.data.valor, event.data.contaId]
  );
});

O efeito e a deduplicação são confirmados juntos.

Limites da transação

Se o handler também envia e-mail, chama gateway e grava em outro banco, a transação local não cobre tudo. Cada efeito externo precisa de estratégia própria.

Idempotency Keys

APIs externas podem aceitar uma chave estável.

await gateway.cobrar({
  valor,
  idempotencyKey: `payment:${event.id}`
});

O provedor deve armazenar o resultado por chave e devolver a mesma resposta em retries.

Outbox

Para publicar eventos após uma mudança local, grave uma Outbox na mesma transação. O envio pode repetir, mas o evento possui ID estável.

Leia Outbox Pattern no Node.js.

Kafka Transactions

Kafka oferece transações para coordenar leitura, produção e offsets dentro do próprio ecossistema. Isso ajuda em pipelines Kafka-to-Kafka, mas não torna uma chamada HTTP ou gravação externa automaticamente exactly-once.

Deduplicação

A tabela Inbox deve ter chave única e retenção compatível com a possibilidade de redelivery. Apagar IDs cedo demais permite duplicatas antigas.

Chave de negócio

Às vezes o ID da mensagem muda durante retry. Uma chave de negócio pode ser mais adequada, como pedidoId + tipoDaOperação.

UNIQUE (order_id, operation_type)

Operações naturalmente idempotentes

Definir estado absoluto é mais fácil de repetir do que aplicar incremento.

// Mais seguro para repetição
UPDATE pedidos SET status = 'confirmado' WHERE id = $1;

// Exige deduplicação
UPDATE estoque SET quantidade = quantidade - 1 WHERE sku = $1;

Comutatividade

Algumas operações podem ser desenhadas para produzir o mesmo resultado independentemente da ordem, usando conjuntos, versões e valores máximos.

Concorrência

Duas cópias da mesma mensagem podem chegar simultaneamente. A proteção precisa estar no banco, não apenas em um Set em memória.

Resposta armazenada

Para endpoints idempotentes, salve status, fingerprint e resposta original.

if (registro.completed) {
  return registro.response;
}

Reusar a mesma chave com payload diferente deve gerar conflito.

Observabilidade

Meça duplicatas detectadas, conflitos de chave, tempo de retenção da Inbox, falhas após efeitos externos e retries por operação.

Testes de falha

  • queda antes do efeito;
  • queda depois do efeito e antes do ack;
  • duas entregas simultâneas;
  • retry com mesma chave e payload;
  • mesma chave com payload diferente;
  • redelivery após retenção;
  • timeout da dependência com resultado desconhecido.

Resultado desconhecido

Um timeout não significa que a operação falhou. O servidor remoto pode ter concluído. Consulte o estado usando a mesma chave antes de executar novamente.

Erros comuns

  • afirmar exactly-once sem definir o limite;
  • deduplicação apenas em memória;
  • Inbox fora da transação;
  • chave aleatória em cada retry;
  • não armazenar fingerprint;
  • ignorar efeitos externos;
  • retenção curta;
  • confundir ack com conclusão única.

Conclusão

Exactly-Once no Node.js deve ser tratado como propriedade de um efeito dentro de um limite específico, não como promessa global do sistema. O caminho mais robusto é aceitar redelivery e tornar cada operação idempotente.

Use transações locais, Inbox, Outbox, chaves estáveis e contratos claros com serviços externos. Consulte Idempotency Keys no Node.js e Event-Driven Architecture no Node.js.

Leia a documentação de garantias de entrega do Apache Kafka.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita