Pular para o conteúdo

Idempotência

Você manda um POST para criar uma venda. A conexão cai antes de a resposta voltar. E agora: a venda foi criada ou não?

Sem ajuda, as duas saídas são ruins. Se você repetir, pode criar duas vendas. Se não repetir, pode nunca ter criado nenhuma.

Mande um cabeçalho Idempotency-Key com um valor seu, único para aquela operação:

Terminal window
curl -X POST "https://api.fatureihoje.com/public/v1/clients" \
-H "Authorization: Bearer $FH_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6b7f0e8a-4c21-4d9e-9f3a-71c5d8b2e044" \
-d '{ "name": "Ana Souza" }'

O cabeçalho é o mesmo em toda rota de criação.

A API guarda a resposta junto com essa chave. Se a mesma chave chegar de novo com a mesma requisição, ela devolve a resposta guardada, com o mesmo status e o mesmo corpo, sem executar nada de novo. A resposta repetida vem com o cabeçalho Idempotent-Replayed: true, e é assim que você sabe que o efeito aconteceu na primeira vez.

Então a regra de retry fica simples: repita a mesma requisição, com a mesma chave, quantas vezes precisar.

  • Sempre que um POST cria alguma coisa, e principalmente quando você repete a chamada depois de erro de rede, tempo esgotado, 500 ou 503.
  • Em fila e em job que roda de novo depois de falhar. Gere a chave junto com a tarefa e guarde com ela, não na hora da chamada: gerar a chave a cada tentativa anula o mecanismo.

O valor pode ser qualquer texto. Um UUID versão 4 por operação é a escolha mais simples, e é o que os exemplos abaixo geram.

A chave sozinha não basta. A API também guarda uma impressão digital da requisição, feita de quatro coisas:

  1. o método;
  2. o caminho, em minúsculas e sem barra no fim;
  3. a consulta, a parte depois do ?;
  4. o corpo, comparado por conteúdo e não por texto, então a ordem dos campos do JSON não importa.

Se a chave chegar de novo com a mesma impressão digital, é repetição: a resposta guardada volta. Se a impressão digital for diferente, não é repetição, é engano, e a resposta é 422 idempotency_key_reused.

CódigoStatusQuandoO que fazer
idempotency_key_reused422Mesma chave, requisição diferenteGere uma chave nova para esta operação
idempotency_in_progress409A primeira requisição com essa chave ainda está rodandoEspere alguns segundos e repita a mesma requisição, com a mesma chave

No 409, não gere chave nova. A primeira chamada ainda pode terminar bem, e uma chave nova executaria a operação uma segunda vez, que é exatamente o que este mecanismo existe para evitar.

Se o processamento falhar, a chave é liberada na hora, e a próxima tentativa com ela roda de verdade. A reserva também tem prazo: se o processo morrer no meio, ela expira em poucos minutos e a chave volta a aceitar tentativa, em vez de ficar travada por um dia.

LimiteValor
Por quanto tempo a resposta fica guardada24 horas
Tamanho máximo do registro guardado256 KB
Tamanho máximo do corpo da requisição1 MB

As 24 horas contam a partir da primeira chamada. Passado esse prazo, a mesma chave com a mesma requisição executa de novo, porque não há mais nada guardado para devolver.

O teto de 256 KB é da resposta guardada, não da requisição. Resposta maior que isso não fica guardada por inteiro: a API lembra que a operação já rodou, e a repetição recebe 409 em vez de repetir a resposta. O efeito aconteceu uma vez só, que é o que importa, mas você precisa ler o recurso para saber como ele ficou.

A resposta guardada vale para o acesso de quando ela foi gerada. Se a chave ou o membro que ela representa perderam permissão desde então, a repetição não devolve aquele corpo: sem a permissão da rota a resposta é 403, e com a rota ainda liberada mas o acesso mudado (por exemplo, a chave perdeu finance:read e a venda guardada trazia os lançamentos) a resposta é 409 conflict. Nos dois casos nada é executado de novo; leia o recurso para saber como ele ficou.

O teto de 1 MB é do corpo da requisição e vale para toda chamada, com ou sem Idempotency-Key. Acima dele a resposta é 413 request_too_large.

Duas chaves de API diferentes têm espaços de idempotência separados. O mesmo valor de Idempotency-Key em duas chaves são duas operações, não uma repetição, e nenhuma enxerga a resposta guardada da outra.

Isso é bom de um lado: dois sistemas da mesma empresa podem gerar o valor do jeito deles sem combinar nada.

O mesmo vale para revogar uma chave e criar outra: é uma chave nova, é um espaço novo.

Terminal window
KEY=$(uuidgen | tr 'A-Z' 'a-z')
echo "$KEY"

uuidgen vem no macOS e na maioria das distribuições Linux. Onde ele não existe, serve cat /proc/sys/kernel/random/uuid.

  • Erros: o catálogo inteiro, com os dois códigos de idempotência.
  • Autenticação: como a rotação de chave funciona, que é a armadilha acima.