GH GambleHub

Exactly-once vs At-least-once

1) Porquê discutir semânticos

A semântica de entrega determina a frequência com que o destinatário verá a mensagem em casos de falhas e retalhos:
  • At-se-once - sem repetição, mas pode ser uma perda (raramente aceitável).
  • At-least-once - não perdemos, mas pode ser duplicado (default da maioria dos corretores/filas).
  • Exactly-once - Cada mensagem é processada exatamente uma vez em termos de efeitos observados.

Verdade chave: em um mundo distribuído, sem transações globais e coerência sincronizada «puro» end-to-end exactly-once não é possível. Construímos efetivamente exactly-once, permitimos repetições nos transportes, mas fazemos o processamento de forma que o efeito observado seja «como se fosse uma vez».


2) Modelo de falha e onde as duplicações surgem

As repetições aparecem por causa de:
  • Perdas ack/commit (produtor/corretor/consumer «não ouviu» confirmação).
  • Reapresentações de líderes/réplicas, restaurações após quebras de rede.
  • Temporizações/retrações em qualquer área (kliyent→broker→konsyumer→sink).

Consequência: não se pode confiar na «exclusividade do transporte». Gerenciamos os efeitos de gravar no banco de dados, cancelar dinheiro, enviar e-mails, etc.


3) Exactly-once em fornecedores e o que é realmente

3. 1 Kafka

Dá tijolos:
  • Idempotent Producer (`enable. idempotence = true ') - Impede as duplicações no lado do produtor em retais.
  • Transações - Atômico publicam mensagens em várias partituras e comensais de consumo (pattern read-processo-write sem «omissões»).
  • Competition - armazena o último valor da chave.

Mas «fim da cadeia» (BB/pagamento/correio) ainda requer idempotidade. Caso contrário, a tomada do processador vai causar efeitos.

3. 2 NATS / Rabbit / SQS

O padrão é at-least-once com ack/redelivery. Exactly-once é alcançado ao nível da aplicação: chaves, deadup stor, upsert.

Conclusão: exactly-once transporte ≠ efeito exactly-once. Este último é feito no processador.


4) Como construir efetivamente exactly-once sobre at-least-once

4. 1 Chave Idempotente (idempotency key)

Cada comando/evento traz a chave natural: 'payment _ id', 'order _ id # step', 'saga _ id # n'. Processador:
  • A verificar «já viste?» - Redis/BD com TTL/Retensor.
  • Se viu, repete o resultado calculado anteriormente ou faz no-op.
Redis-desenho:
lua
-- SET key if not exists; expires in 24h local ok = redis.call("SET", KEYS[1], ARGV[1], "NX", "EX", 86400)
if ok then return "PROCESS" else return "SKIP" end

4. 2 Upsert na base de dados (sink idimpotente)

As gravações são feitas através do UPSERT/ON CONFLICT, verificando a versão/valor.

PostgreSQL:
sql
INSERT INTO payments(id, status, amount, updated_at)
VALUES ($1, $2, $3, now())
ON CONFLICT (id) DO UPDATE
SET status = EXCLUDED.status,
updated_at = now()
WHERE payments.status <> EXCLUDED.status;

4. 3 Outbox transacionado/Inbox

Outbox: transação de negócios e gravação de eventos para publicação ocorrem em uma transação de banco de dados. O publisher de fundo lê o outbox e envia para o corretor → não há discrepância entre o estado e o evento.
Inbox: Para os comandos de entrada, salvamos 'mensagem _ id' e o resultado até a execução; reaproveitamento vê gravação e não repete efeitos colaterais.

4. 4 Processamento de cadeia consistente (read→process→write)

Kafka: A transação «leu o ofset → gravou os resultados → Comit» em um único bloco atômico.
Sem transações: «Primeiro anote o resultado/Inbox, depois ack»; com o crash, a duplicação verá o Inbox e termina no-op.

4. 5 SAGA/compensação

Quando a idimpotência não é possível (o provedor externo descontou o dinheiro), usamos operações de compensação (refund/void) e APIs externas idempotentes («POST» com o mesmo «Idempotency-Key» dá o mesmo resultado).


5) Quando é suficiente at-least-once

Atualizações em dinheiro/visualizações materializadas com a chave.
Contadores/métricas onde a incorporação é aceitável (ou armazenamos delta com versão).
Notificações onde a carta secundária não é crítica (é melhor colocar a chave na mesma).

Regra: Se a tiragem não alterar o significado do negócio ou facilmente descobrir at-least-once + proteção parcial.


6) Desempenho e custo

Exactly-once (mesmo «eficiente») é mais caro: gravações extras (Inbox/Outbox), armazenamento de chaves, transações, mais difícil de diagnosticar.
At-least-once é mais barato/fácil, melhor por throughput/p99.
Avalie o preço da dupla x probabilidade de duplicação vs custo de proteção.


7) Exemplos de configuração e código

7. 1 Produtor Kafka (Idempotidade + transações)

properties enable.idempotence=true acks=all retries=INT_MAX max.in.flight.requests.per.connection=5 transactional.id=orders-writer-1
java producer.initTransactions();
producer.beginTransaction();
producer.send(recordA);
producer.send(recordB);
// также можно atomically commit consumer offsets producer.commitTransaction();

7. 2 Consoante com Inbox (pseudocode)

pseudo if (inbox.exists(msg.id)) return inbox.result(msg.id)
begin tx if!inbox.insert(msg.id) then return inbox.result(msg.id)
result = handle(msg)
sink.upsert(result)     # идемпотентный синк inbox.set_result(msg.id, result)
commit ack(msg)

7. 3 HTTP Idempotency-Key (API externas)


POST /payments
Idempotency-Key: 7f1c-42-...
Body: { "payment_id": "p-123", "amount": 10.00 }

O POST repetido com a mesma chave → o mesmo resultado/status.


8) Observabilidade e métricas

'duplicate _ attempts _ total' - quantas vezes apanharam a tomada (Inbox/Redis).
'idempotency _ hit _ rate' é a proporção de repetições «resgatadas» pela idimpotência.
'txn _ abort _ rate' (Kafka/BD) é a proporção de reembolsos.
'outbox _ backlog' é um atraso na publicação.
'exactly _ once _ path _ latency fnp95, p99' vs 'at _ least _ once _ path _ latency' - despesas gerais.
Auditar logs: vinculação 'mensagem _ id', 'idempotency _ key', 'saga _ id', 'attempt'.


9) Playbooks de teste (Game Days)

Retrai da produtora em temporais artificiais.
Crash entre «sink e ack»: certifique-se de que o Inbox/Upsert impede a tomada.
Entrega pere: aumentar redelivery no corretor; Verificar o Dedup.
Idempotidade das APIs externas: POST repetido com a mesma chave - a mesma resposta.
Mudança de líder/quebra de rede: verificar transações Kafka/comportamento dos consórcios.


10) Anti-pattern

«Temos um Kafka com exactly-once, então podemos sem as chaves».
No-op ack antes de gravar: Ackar, mas o sink caiu → perda.
Falta de DLQ/retrações com jitter, repetições sem fim e tempestade.
UUID aleatório em vez de chaves naturais, não há como deduzir.
Mistura Inbox/Outbox com tabelas de prod sem índices: bloqueios quentes e colas p99.
Transações de negócios sem API idumpotente em provedores externos.


11) Folha de cheque de seleção

1. Preço da dupla (dinheiro/legal/UX) vs preço de proteção (latência/complexidade/custo).
2. Há uma chave natural de evento/operação? Se não, inventa um estável.
3. O Sink suporta Upsert/versioning? Senão, Inbox + compensações.
4. São necessárias transações globais? Se não, segmente para SAGA.
5. Precisa de réplicas/retenções de longa duração? Kafka + Outbox. Precisa de RPC rápido/atraso baixo? NATS + Idempotency-Key.
6. Multi-tenência e quotas: isolamento de chaves/espaços.
7. Observabilidade: métricas de idempotency e backlog incluídas.


12) FAQ

Q: É possível alcançar um exactly-once «matemático» end-to-end?
A: Apenas em cenários estreitos com um único armazenamento e transações consistentes em todo o caminho. Em geral, não; use efetivamente o exactly-once através da idempotidade.

O que é mais rápido?
A: At-least-once. Exactly-once adiciona transações/armazenamento de chaves → acima de p99 e custo.

Onde guardar as chaves de idempotação?
A: Store rápido (Redis) com TTL ou tabela Inbox (PK = mensagem _ id). Para pagamentos - mais (dias/semanas).

Q: Como escolher as chaves TTL?
A: Mínimo = tempo máximo de reaproveitamento + reserva operacional (normalmente 24-72 h). Para as finanças, mais.

Preciso de uma chave se eu tiver a minha chave Kafka?
A: Sim. A competência reduzirá o armazenamento, mas não tornará o seu sink idumpotente.


13) Resultado

At-least-once é uma semântica básica e confiável de transporte.
Exactly-once como um efeito de negócio é alcançado ao nível do processador: Idempotency-Key, Inbox/Outbox, Upsert/versões, SAGA/compensações.
A escolha é um compromisso custo ↔ risco de duplicação ↔ facilidade de operação. Projete as chaves naturais, torne os sinks idempotantes, adicione a observação e realize regularmente o game days - então os seus piplines serão previsíveis e seguros, mesmo com a tempestade de retalhos e falhas.

Contact

Entrar em contacto

Contacte-nos para qualquer questão ou necessidade de apoio.Estamos sempre prontos para ajudar!

Iniciar integração

O Email é obrigatório. Telegram ou WhatsApp — opcionais.

O seu nome opcional
Email opcional
Assunto opcional
Mensagem opcional
Telegram opcional
@
Se indicar Telegram — responderemos também por lá.
WhatsApp opcional
Formato: +indicativo e número (ex.: +351XXXXXXXXX).

Ao clicar, concorda com o tratamento dos seus dados.