Ir para o conteúdo principal

Reexecutar uma requisição sem duplo efeito

A idempotência permite reexecutar uma requisição POST sem se preocupar com um duplo efeito. Um cliente que perde a resposta por timeout pode tentar de novo com a mesma chave de idempotência, e a API retorna a resposta armazenada em vez de executar o handler outra vez. A CaptainDNS segue a convenção da Stripe, com uma janela de validade de 24 horas.

Por que usar idempotência

As redes nunca são totalmente confiáveis. Um cliente pode:

  • Enviar uma requisição que dá certo no servidor, mas cuja resposta se perde no caminho.
  • Cair no meio de um retry automático depois de uma falha.
  • Ver um timeout do load balancer quando a API já processou a requisição.

Sem idempotência, você tem que escolher entre:

  • Não tentar de novo, arriscando perder operações legítimas.
  • Tentar de novo às cegas, arriscando executar a mesma operação várias vezes.

A idempotência oferece um terceiro caminho: tentar de novo com segurança.

Como usar

Adicione um header Idempotency-Key à sua requisição POST:

POST /public/v1/deliverability/score HTTP/1.1
Host: api.captaindns.com
Authorization: Bearer cdns_live_a3f2XK7mN9QrVtZ4yP1sH6eL8cF2dB5aR3gW7kJxM
Content-Type: application/json
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

{"email":"contact@captaindns.com"}

A chave deve ser uma string opaca com 1 a 255 caracteres ASCII imprimíveis, sem espaço. Os UUID v4 ou ULID continuam recomendados como boa prática (entropia suficiente, colisões estatisticamente impossíveis), mas qualquer identificador único do lado do cliente serve.

O que acontece no servidor

Na primeira requisição:

  1. A CaptainDNS calcula o SHA-256 do corpo da requisição.
  2. Depois do sucesso, ela guarda a associação entre a chave de idempotência e o hash do corpo.
  3. Ela captura o status e o corpo da resposta antes de devolvê-la ao cliente.

Na segunda requisição com a mesma chave:

  1. A CaptainDNS encontra um registro existente.
  2. Se o hash do novo corpo corresponde ao armazenado: replay. A resposta armazenada é retornada exatamente como está, com o header X-Idempotent-Replay: true.
  3. Se o hash é diferente: conflito. A API retorna 409 IDEMPOTENCY_CONFLICT com uma mensagem explícita.

O replay não dispara o processamento por trás e não grava uma segunda linha de uso: a resposta armazenada volta exatamente como está. Mas ele atravessa as etapas que vêm antes dele. O controle de créditos e o token bucket por chave se aplicam antes da idempotência entrar em ação: um replay consome, portanto, um token de rate limit e um crédito, igual a uma chamada comum. A idempotência protege você do processamento duplicado, não da cobrança duplicada. Dimensione seus retries levando isso em conta.

Janela de validade

Os registros de idempotência duram 24 horas. Depois desse prazo, eles são apagados e uma chave reutilizada dispara um novo processamento normalmente.

Essa janela é um meio-termo: curta demais, e você perde a vantagem dos retries adiados; longa demais, e você acumula um volume cada vez maior de respostas antigas. 24 horas cobrem a maioria dos padrões de retry: o retry imediato depois de um erro de rede, o retry nos minutos seguintes à recuperação de um job, e o retry agendado de madrugada depois de uma falha detectada no monitoramento.

Métodos cobertos

A idempotência se aplica só a métodos que alteram estado ou são custosos. Na prática, todos os endpoints públicos da CaptainDNS são POST, então todos são elegíveis. As requisições GET não passam pelo mecanismo de idempotência: elas já são idempotentes por natureza no nível HTTP.

O header Idempotency-Key é opcional. Se você não enviar, a requisição é processada normalmente, sem garantia de replay.

Conflitos

Um conflito 409 IDEMPOTENCY_CONFLICT acontece quando:

  • Você reutiliza uma chave de idempotência para uma requisição cujo corpo é diferente, mesmo que seja só um caractere.
  • Isso pode acontecer se o seu cliente regenerar a requisição entre duas tentativas (timestamps, UUID embutidos, ordem das chaves JSON).

Para evitar conflitos:

  • Calcule a chave de idempotência antes de serializar o corpo, não depois.
  • Não misture chamadas diferentes sob a mesma chave.
  • Se o seu framework reordena as chaves JSON, fixe a ordem manualmente.

Em caso de conflito legítimo (por exemplo, você realmente quer reexecutar uma operação diferente), gere uma nova chave.

Exemplos de uso

Retry após timeout de rede

idempotencyKey = uuid()
for attempt in 1..3:
    try:
        response = post(url, body, headers={"Idempotency-Key": idempotencyKey})
        return response
    except TimeoutError:
        sleep(2 * attempt)
raise

Cada retry usa a mesma chave. Se uma das chamadas já tiver dado certo no servidor, as seguintes retornam a resposta armazenada.

Deduplicação em uma fila de jobs

job = fetch_next_job()
idempotencyKey = hash(job.id)
response = post(url, job.payload, headers={"Idempotency-Key": idempotencyKey})
mark_job_done(job.id, response)

Se a fila redistribuir um job depois de uma falha, a segunda execução do mesmo id de job reutiliza a mesma chave e recupera a resposta armazenada, em vez de cobrar o cliente duas vezes.

Batch com deduplicação no servidor

for domain in domains:
    idempotencyKey = "batch-" + run_id + "-" + domain
    post(url, {"domain": domain}, headers={"Idempotency-Key": idempotencyKey})

Se o batch for reiniciado depois de uma falha, cada domínio já processado vai direto para o replay, e só os que faltam consomem créditos.

Limitações

  • Armazenamento limitado a 24 h: depois desse prazo, o replay não é mais possível. Jobs muito adiados devem armazenar as respostas do lado do cliente.
  • Escopo por chave de API: a mesma chave de idempotência usada por duas chaves de API diferentes não colide. Isso é proposital: cada chave tem seu próprio espaço de nomes independente.
  • Sem retomada entre servidores: se você usa vários backends distintos, eles compartilham a mesma API, mas precisam coordenar as chaves de idempotência entre si.
  • Identidade estrita do corpo: um espaço a mais quebra o hash e dispara um conflito. Serialize de forma determinística.

Próximos passos

Continue com os códigos de erro para entender como reagir a 409 IDEMPOTENCY_CONFLICT, ou consulte a referência OpenAPI para ver o header documentado em cada endpoint público.