Idempotência
Como tornar seguras as operações repetidas quando a rede falha no meio do pedido.
As decisões de idempotência são cinco: quais operações precisam dela, onde guardar as chaves, por quanto tempo, como tratar pedidos simultâneos e como lidar com efeitos fora do banco.
Quais operações precisam
| Operação | Precisa de chave? |
|---|---|
| Pagamentos, reembolsos, transferências | Sim, sempre |
| Criação de pedidos, reservas, emissão de documentos | Sim |
| Envio de e-mails, SMS, notificações | Sim, ou deduplicação pelo destinatário e assunto |
PUT e DELETE bem implementados | Não: já são idempotentes |
POST que só registram algo inofensivo de repetir, como um evento de visualização | Geralmente não |
Onde guardar as chaves
| Onde | Vantagens | Cuidados |
|---|---|---|
| No banco principal, na mesma transação do efeito | A chave e o efeito são gravados juntos ou não são gravados; nenhum estado intermediário | A tabela cresce e precisa de limpeza |
| No Redis | Rápido, com expiração automática | A chave e o efeito estão em sistemas diferentes: se o processo cair entre os dois, fica inconsistente |
| No middleware ou gateway | Uma implementação para todas as rotas | O gateway não sabe se o efeito no banco aconteceu |
O banco principal, na mesma transação, é a escolha mais segura sempre que o efeito também está nele. Foi a abordagem testada nas cinco linguagens. O Redis faz sentido como camada extra para recusar repetições muito rápidas, mas não substitui a garantia do banco.
O que guardar
| Campo | Para quê |
|---|---|
| A chave | Identificar a operação |
| Um hash do corpo do pedido | Recusar a mesma chave com conteúdo diferente |
| O código de status e o corpo da resposta | Devolver exatamente a mesma resposta nas repetições |
| Quem enviou (usuário ou cliente da API) | Evitar que a chave de um cliente colida com a de outro |
| Data de criação | Limpeza das chaves antigas |
A chave deve ser única por cliente, e não globalmente: duas empresas que integram a mesma API podem gerar o mesmo valor por coincidência ou por um defeito. Uma restrição única em (cliente_id, chave) resolve.
Por quanto tempo
As chaves precisam viver mais que o maior intervalo em que um cliente pode tentar de novo. Um prazo de 24 horas é comum em APIs de pagamento. Depois disso, uma limpeza periódica remove as antigas:
DELETE FROM chaves_idempotencia WHERE criada_em < now() - interval '24 hours';Pedidos simultâneos com a mesma chave
| Abordagem | Comportamento | Quando usar |
|---|---|---|
| Esperar a primeira terminar | O segundo pedido fica parado até o primeiro confirmar, e recebe a mesma resposta | Operações rápidas, totalmente dentro do banco |
Responder 409 enquanto processa | O segundo pedido recebe 409 Conflict e tenta de novo mais tarde | Operações demoradas, que não podem prender uma transação |
A primeira é a do exemplo testado: o próprio banco faz a segunda transação esperar na inserção da chave. A segunda exige gravar a chave com um estado "em processamento" e confirmar antes de fazer o trabalho, o que leva ao próximo ponto.
Efeitos fora do banco
O exemplo testado cria o pagamento no próprio banco. Na prática, um pagamento envolve chamar um provedor externo, e essa chamada não cabe numa transação do banco: segurar uma transação aberta enquanto espera uma API externa prende conexões e travas, como alerta o tópico de transações.
| Etapa | O que fazer |
|---|---|
| 1. Reservar a chave | Gravar a chave com estado processando e confirmar |
| 2. Chamar o provedor | Repassar uma chave de idempotência ao provedor, derivada da sua |
| 3. Gravar o resultado | Atualizar a chave com estado concluido e a resposta, junto com o registro do pagamento |
| Se o processo cair entre 2 e 3 | Uma nova tentativa encontra a chave em processando, chama o provedor de novo com a mesma chave dele e recebe o resultado da primeira cobrança, sem cobrar de novo |
A segunda linha é o que torna o processo inteiro seguro: os provedores de pagamento sérios aceitam chaves de idempotência. Repassar uma chave derivada da sua garante que, mesmo se o seu sistema chamar o provedor duas vezes, a cobrança aconteça uma vez.
O lado do cliente
| Prática | Por quê |
|---|---|
| Gerar a chave uma vez por operação, antes da primeira tentativa | As novas tentativas precisam da mesma chave |
| Guardar a chave até receber uma resposta definitiva | Se o aplicativo for fechado e reaberto, a nova tentativa usa a mesma chave |
| Repetir só com chave, e com intervalo crescente | Novas tentativas sem chave em POST são cobranças duplas esperando acontecer |
Tratar 409 como "tente de novo em instantes" | A operação ainda está em andamento |
Tratar 422 de chave reutilizada como erro do próprio cliente | Uma chave nova foi esquecida |
Resumo da decisão
- Toda operação que cria ou cobra aceita uma chave de idempotência.
- Chave e efeito na mesma transação, com restrição única, nunca "conferir e depois gravar".
- Guardar a resposta e o hash do corpo; recusar a mesma chave com outro conteúdo.
- Chave por cliente, com validade de cerca de 24 horas e limpeza periódica.
- Efeitos externos: estado intermediário e chave repassada ao provedor.
- No cliente: uma chave por operação, mantida durante as novas tentativas.
O próximo nível mostra a implementação nas cinco linguagens, testada com 20 pedidos simultâneos.
Prompts para aprofundar
Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.
Desenhar a idempotência da minha API
Quero desenhar o suporte a chaves de idempotência na minha API. Pergunte qual linguagem, framework e banco eu uso, quais rotas criam ou cobram algo e se há chamadas a serviços externos, como provedores de pagamento. Ajude a decidir: 1. Quais rotas exigem a chave. 2. Onde guardar as chaves e o que guardar com elas. 3. Por quanto tempo e como limpar. 4. O comportamento com pedidos simultâneos: esperar ou responder 409. 5. Como tratar efeitos externos com estado intermediário e chave repassada ao provedor. Termine com o desenho da tabela e o fluxo de cada caso.
Idempotência com um provedor de pagamento
Minha API chama um provedor de pagamento externo e quero garantir que nenhuma falha cause cobrança dupla. Pergunte qual provedor eu uso e como a chamada é feita hoje. Explique: 1. Por que não chamar o provedor dentro de uma transação do banco. 2. O fluxo com estado intermediário: reservar a chave, chamar o provedor, gravar o resultado. 3. Como derivar e repassar a chave de idempotência para o provedor. 4. O que acontece em cada ponto de falha possível, e como a nova tentativa se recupera. 5. Como reconciliar com os registros do provedor, para pegar qualquer caso que escape.
Clientes que repetem com segurança
Quero que os clientes da minha API, ou os meus serviços que chamam outras APIs, repitam pedidos com segurança. Pergunte qual linguagem e biblioteca de HTTP eu uso. Explique, com código: 1. Como gerar a chave uma vez por operação e mantê-la durante as novas tentativas. 2. Como guardar a chave para sobreviver a um reinício do aplicativo. 3. Quais respostas devem gerar nova tentativa (tempo esgotado, 409, 5xx) e quais não (4xx, exceto 409). 4. O intervalo crescente entre tentativas. 5. O erro de gerar uma chave nova a cada tentativa, e como um teste pega esse erro.