Pular para o conteúdo

Idempotência

Iniciante10 min de leiturafundamentos

Uma operação é idempotente quando executá-la uma vez ou dez vezes produz o mesmo estado final. mkdir -p /opt/app é idempotente; mkdir /opt/app não é — a segunda execução falha. A distinção parece pedante até o dia em que a rede cai no meio de um apply e você precisa decidir se pode rodar de novo.

É aí que a propriedade mostra o valor real: idempotência é o que torna a repetição segura, e repetição é a única coisa que a automação sabe fazer bem diante de falha.

Sistemas distribuídos falham no meio, sempre. O cliente envia uma requisição, a rede cai antes da resposta chegar, e ele não sabe se a operação aconteceu. Só existem duas saídas: tentar de novo, ou desistir.

Se a operação for idempotente, tentar de novo é gratuito. Se não for, cada tentativa pode criar um recurso duplicado, cobrar o cliente duas vezes ou disparar dois e-mails. É por isso que retry seguro é uma consequência da idempotência, e não uma técnica independente — retry sobre operação não idempotente não é robustez, é dano multiplicado.

O mesmo raciocínio explica por que Kubernetes e Terraform são declarativos: um controlador que reconcilia estado desejado pode rodar continuamente, sem coordenação, porque cada execução converge para o mesmo lugar.

  • No pipeline. Um estágio que executa POST /criar-recurso sem chave de idempotência duplica na primeira reexecução — e reexecutar job é a coisa mais comum do mundo em CI.
  • Em IaC. Um local-exec com curl -X POST, ou uma task Ansible com shell: sem creates:, quebra a propriedade de todo o resto. O apply deixa de ser repetível.
  • Em migrations. Um script sem controle de versão aplicado duas vezes pode duplicar dados ou falhar no meio, deixando o esquema num estado que ninguém previu.
  • Em consumidores de fila. Praticamente toda fila entrega pelo menos uma vez. Se o worker não for idempotente, mensagem reentregue vira cobrança dobrada.

O padrão comum a todos: alguém assumiu que a operação aconteceria exatamente uma vez. Em sistemas distribuídos, “exatamente uma vez” é uma promessa que a infraestrutura não consegue cumprir sozinha — ela é construída em cima de entrega pelo menos uma vez, com idempotência do lado de quem recebe.

Três mecanismos cobrem quase todos os casos:

Verificar antes de agir. É o que os módulos declarativos fazem: consultar o estado atual e agir só na diferença. state: present do Ansible, resource do Terraform, CREATE TABLE IF NOT EXISTS.

Chave de idempotência. O cliente gera um identificador único da intenção e o envia junto; o servidor guarda o resultado da primeira execução e devolve a mesma resposta para repetições da mesma chave. É como APIs de pagamento sérias funcionam.

POST /cobrancas
Idempotency-Key: 9f3a2c-pedido-4821

Operação naturalmente idempotente. SET saldo = 100 é repetível; saldo = saldo + 10 não é. Sempre que a modelagem permitir, prefira declarar o valor final a aplicar um delta.

Idempotência não é gratuita nem sempre possível.

Ela custa estado: a chave de idempotência precisa ser guardada, com prazo de validade e armazenamento, e isso é infraestrutura a mais. Ela também não garante ordem — duas operações idempotentes aplicadas fora de ordem ainda produzem resultado errado; para isso existem versionamento otimista e sequenciamento.

E há efeitos que são irredutivelmente não idempotentes: enviar e-mail, cobrar cartão, disparar uma chamada externa. Nesses casos, a idempotência vive na borda — você registra que já enviou, antes ou junto do envio, e consulta esse registro antes de repetir. O problema não desaparece; ele se move para um lugar onde você consegue tratá-lo.

Por fim, cuidado com o falso idempotente: um script que “verifica antes” com uma consulta e age em seguida tem uma janela entre a verificação e a ação. Sob concorrência, os dois caminhos passam pela verificação e agem. A correção é uma operação atômica no armazenamento (INSERT ... ON CONFLICT, lock, compare-and-swap), não um if melhor escrito.