Idempotência
Fazer a mesma coisa duas vezes deveria ter o mesmo efeito de fazer uma vez só.
Introdução
Uma operação é idempotente quando executá-la várias vezes tem o mesmo efeito observável de executá-la uma única vez. Em sistemas distribuídos, onde retries e entregas duplicadas são inevitáveis, é a idempotência que impede que uma nova tentativa vire uma segunda cobrança, um pedido duplicado ou um saldo dobrado.
Por que existe
A rede não é confiável. O cliente envia uma requisição, o servidor processa, mas a resposta se perde no caminho — então o cliente tenta de novo. Brokers de mensagens entregam pelo menos uma vez (at-least-once), então consumidores veem duplicatas. Sem idempotência, esses eventos normais e esperados causam dano real: cobrar duas vezes, enviar o produto duas vezes, disparar o mesmo e-mail duas vezes. A idempotência existe para tornar o retry seguro — e é isso que torna sistemas confiáveis possíveis.
Analogia
O botão de um elevador bem projetado é idempotente: apertar 'chamar' cinco vezes traz um elevador, não cinco. Apertar de novo enquanto ele já está vindo não muda nada. Compare com um botão ingênuo de 'somar R$ 10' apertado cinco vezes — você ficaria devendo R$ 50. Operações bem desenhadas se comportam como o botão do elevador.
Como funciona
Algumas operações já são naturalmente idempotentes: SET balance = 100 (valor absoluto) ou DELETE user 42 levam ao mesmo estado não importa quantas vezes você aplique. Outras não são: balance = balance + 10 (valor relativo) ou INSERT order produzem um efeito novo a cada execução.
Para tornar uma operação não idempotente segura, o cliente envia uma idempotency key — um token único por operação lógica (por exemplo, um UUID por tentativa de checkout). O servidor guarda essa chave junto com o resultado da primeira execução. Em um retry com a mesma chave, ele devolve o resultado armazenado em vez de executar de novo. A chave precisa ser gravada de forma atômica com o efeito (mesma transação), senão você reabre a janela de duplicação. A semântica do HTTP reflete isso: GET/PUT/DELETE são definidos como idempotentes; POST não é — por isso POSTs de pagamento carregam uma idempotency key.
- 1
O cliente gera uma idempotency key única para a operação e a envia junto com a requisição.
- 2
O servidor consulta o store: essa chave já foi vista antes?
- 3
Se for nova, executa a operação e grava (key → resultado) de forma atômica com o efeito.
- 4
Se já foi vista, devolve o resultado gravado anteriormente, sem executar de novo.
- 5
O cliente pode fazer retry quantas vezes precisar; o resultado é idêntico todas as vezes.
Interativo
Arquitetura
A idempotency key deduplica uma requisição repetida
Verifica a chave
Store de dedupe (atômico com o efeito)
Primeira chamada: executa + grava (key, resultado) de forma atômica. Retry com a mesma key: devolve o resultado guardado, sem cobrar de novo.
No código
export async function charge(req: ChargeRequest, idempotencyKey: string) {
return db.transaction(async (tx) => {
// Reivindicação atômica: unique constraint em idempotency_key.
const existing = await tx.idempotency.findByKey(idempotencyKey)
if (existing) return existing.result // retry → devolve o resultado guardado
const result = await processPayment(tx, req) // o efeito colateral de verdade
// Gravado na MESMA transação do efeito — sem janela de duplicação.
await tx.idempotency.insert({ key: idempotencyKey, result })
return result
})
}public async Task Handle(OrderPlaced evt)
{
// Dedupe pelo id da mensagem/evento; INSERT ... ON CONFLICT DO NOTHING.
var isNew = await _processed.TryMarkAsync(evt.EventId);
if (!isNew) return; // entrega duplicada → no-op
await _orders.CreateAsync(evt); // seguro: roda no máximo uma vez por EventId
}No mundo real
Onde você já viu isso
A API da Stripe exige o header Idempotency-Key em requisições de cobrança exatamente por isso: um cliente cuja conexão caiu pode refazer a cobrança com segurança, e a Stripe devolve o resultado original em vez de cobrar duas vezes. É o mesmo padrão que sustenta o processamento 'exactly-once' em sistemas de eventos — que, no fundo, é entrega at-least-once somada a consumers idempotentes.
Quando usar
Recorra a isso quando
- Qualquer operação com efeito colateral acessível por uma rede não confiável (pagamentos, pedidos).
- Consumers de brokers at-least-once (Kafka, SQS, RabbitMQ).
- APIs públicas em que o cliente vai fazer retry ao dar timeout.
- Jobs em background e webhooks que podem ser reprocessados.
Evite quando
- Operações naturalmente idempotentes (SET absoluto, DELETE) que não precisam de mecanismo extra.
- Leituras puras, sem efeito colateral.
- Quando o store de dedupe realmente não compensa para uma ação de baixo risco e fácil de corrigir.
Trade-offs
Vantagens
- Torna retries e entregas duplicadas seguros — a base da confiabilidade.
- Permite semântica 'exactly-once' em cima de uma infraestrutura at-least-once.
- Simplifica o cliente: ele pode refazer a chamada às cegas em caso de falha.
Desvantagens
- Exige guardar e expirar chaves (estado extra).
- A chave precisa ser gerada e escopada corretamente pelo cliente.
- A atomicidade entre chave e efeito é fácil de errar de forma sutil.
- Chaves têm janela de retenção; retries muito atrasados podem reexecutar.
A idempotência troca um pouco de estado e complexidade extra pela capacidade de fazer retry com segurança — quase sempre vale a pena para operações com efeito colateral. O desenho da chave é, em si, um trade-off: chaves geradas pelo cliente são corretas, mas exigem cooperação do cliente; chaves naturais (id do pedido) evitam armazenamento, mas só funcionam quando existe um identificador de negócio estável. A retenção também é um trade-off: guarde as chaves tempo suficiente para cobrir retries realistas, mas não para sempre. E 'exactly-once' é, na real, 'entrega at-least-once + processamento idempotente' — fingir o contrário leva a designs frágeis.
| Garantias de entrega/processamento | At-most-once | At-least-once | Exactly-once (efetivo) |
|---|---|---|---|
| Duplicatas | Nunca | Possíveis | Removidas por dedupe |
| Mensagens perdidas | Possíveis | Nunca | Nunca |
| Como | Dispara e esquece | Retry até o ack | At-least-once + consumer idempotente |
| Use quando | Métricas que você pode perder | Maioria dos eventos de negócio | Pagamentos, pedidos, dinheiro |
Erros comuns
Fique atento a
- Gravar a idempotency key em um passo separado do efeito, reabrindo a janela de duplicação.
- Gerar a chave no servidor a cada requisição (fazendo o retry parecer uma chamada nova).
- Reaproveitar uma mesma chave para operações logicamente diferentes.
- Não ter unique constraint, permitindo que duas primeiras tentativas concorrentes executem as duas.
- Confundir entrega at-least-once com exactly-once — o consumer ainda precisa deduplicar.
Pensando em falhas
O que quebra
Uma requisição dá timeout, mas na verdade foi concluída com sucesso. O que acontece com e sem idempotência?
O cliente refaz a chamada. Sem idempotência, o servidor executa de novo — cobrança dobrada. Com uma idempotency key, o servidor reconhece a chave, pula a execução e devolve o resultado original. É exatamente esse cenário (resposta perdida, e não requisição perdida) que faz a idempotência importar mais do que ajustar timeouts.
Um consumer processa a mesma mensagem duas vezes (entrega at-least-once). Como manter a correção?
O consumer precisa ser idempotente: deduplicar pelo id da mensagem/evento antes de aplicar o efeito, de preferência gravando o id de forma atômica com a escrita. Aí uma reentrega vira no-op. Tentar alcançar exactly-once só no broker é uma armadilha — a resposta prática é at-least-once + consumer idempotente.
Pense como sênior
Visão de Engenheiro(a) Sênior
A frase clássica de quem é sênior: 'fazer retries sem garantir idempotência pode executar a mesma operação mais de uma vez e gerar efeitos duplicados.' Se você propõe retry em qualquer ponto, a frase seguinte já tem que ser como aquele alvo foi tornado idempotente.
Visão de Engenheiro(a) Sênior
Empurre a idempotência para a camada que é dona do efeito colateral e grave a chave na mesma transação do efeito. Pendurar o dedupe em um cache separado deixa uma janela de crash que reintroduz duplicatas.
Lembre-se
Fazer retries sem idempotência pode duplicar efeitos colaterais.
Lembre-se
Grave a idempotency key de forma atômica com o efeito, senão o dedupe é uma mentira.
Questões de entrevista
Projete um endpoint de pagamento idempotente.
Recordação ativa
Teste-se
Your payment API receives the same request twice because the client timed out and retried. What is the best protection against a double charge?
Desafios práticos
Endpoint de pagamento com Idempotency-Key
Construir um endpoint de pagamento que seja seguro sob retries duplicados e concorrentes.
Requisitos
- Aceitar um header Idempotency-Key.
- Executar a cobrança e persistir key+resultado em uma única transação.
- Devolver o resultado original em qualquer retry.
Restrições
- Uma unique constraint na chave.
- Precisa lidar com duas primeiras tentativas concorrentes.
Critérios de aceite
- Retries sequenciais → uma cobrança, mesma resposta.
- Duplicatas concorrentes → uma cobrança, e ambos os chamadores recebem o mesmo resultado.
Casos extremos
- Crash entre a cobrança e o insert da chave (deve ser impossível se for atômico).
- Chave reutilizada para um valor diferente.
Bônus
- Adicionar retenção/expiração de 24h para as chaves.
- Devolver uma resposta clara para uma duplicata em andamento.
Reflexão
- Por que a chave precisa ser gravada na mesma transação da cobrança?
Desafio de arquitetura
Um checkout chama o Payment, que emite um evento PaymentSucceeded consumido pelos serviços de Order e Email através de um broker at-least-once. Projete a idempotência ponta a ponta para que um retry do cliente, uma reentrega do Payment e uma reentrega do Order, juntos, nunca consigam cobrar duas vezes, criar dois pedidos ou enviar dois recibos. Onde mora cada dedupe e qual é a chave em cada salto?
Flashcards
Resumo
A idempotência torna repetir uma operação inofensivo, o que é essencial porque retries e entregas duplicadas são garantidos em sistemas distribuídos. Operações naturalmente idempotentes não precisam de nada; para as demais, uma idempotency key fornecida pelo cliente e gravada de forma atômica com o efeito deduplica os retries. Processamento 'exactly-once' é simplesmente entrega at-least-once somada a um consumer idempotente.
Pontos-chave
- Idempotente = fazer N vezes é igual a fazer uma vez (do ponto de vista observável).
- Use uma idempotency key gravada na mesma transação do efeito colateral.
- Entrega at-least-once + consumer idempotente = exactly-once efetivo.
- Nunca adicione retries sem tornar o alvo idempotente.
Suas anotações
Salvo neste dispositivo