Idempotidade e chaves
O que é idempotação
Idempotidade é uma propriedade de operação em que uma repetição com o mesmo ID não altera o efeito final. Nos sistemas distribuídos, esta é a principal forma de tornar o resultado equivalente a «exatamente um processamento», apesar de retratos, mensagens duplicadas e temporizações.
A ideia-chave é que cada operação potencialmente repetida deve ser marcada por uma chave em que o sistema reconheça «isso já foi feito» e aplica o resultado no máximo uma vez.
Onde isso é importante
Pagamentos e balanços: débitos/inscrições por 'operation _ id'.
Reservas/quotas/limites: mesmo slot/recurso.
Webhooks/notificações: Uma nova entrega não deve duplicar o efeito.
Importação/migração: reaproveitamento de arquivos/pacotes.
Processamento de strim: duplas de corretor/CDC.
Tipos de chaves e seu alcance
1. Operation key - ID de uma tentativa específica de transação
Os exemplos são 'idempotency _ key' (HTTP), 'operation _ id' (RPC).
Área: serviço/unidade; armazenado na tabela de dedução.
2. Event key - Identificador único de evento/mensagem
Exemplos: 'event _ id' (UUID), '(producer _ id, sequence)'.
Área: consumidor/grupo de consumidores; protege projeções.
3. Business Key - a chave natural da área de objetos
Exemplos de 'payment _ id', 'invoice _ number', '(user _ id, day)'.
Área: unidade; é aplicado em verificações de exclusividade/versão.
TTL e política de armazenamento
A TTL de chaves ≥ uma possível janela de repetição: retenção de logs + atrasos de rede/processo.
Para domínios críticos (pagamentos) TTL - dias/semanas; para a telemetria, um relógio.
Limpe as tabelas de background jobs; para auditoria - arquive.
Armazenamento de chaves (dedução)
BD transacional (recomendado): índice upsert/unique confiável, transação conjunta com efeito.
KV/Redis: rápido, conveniente para um TTL curto, mas sem transação conjunta com OLTP - cuidado.
State store processador estrim: local + chainjog no corretor; bem em Flink/KStreams.
- idempotency_keys
`consumer_id` (или `service`), `op_id` (PK на пару), `applied_at`, `ttl_expires_at`, `result_hash`/`response_status` (опц.) .
Índice: '(consumer _ id, op _ id)' é único.
Técnicas de implementação básicas
1) Transação «efeito + progresso»
Gravar o resultado e fixar o progresso da leitura/posição - em uma transação.
pseudo begin tx if not exists(select 1 from idempotency_keys where consumer=:c and op_id=:id) then
-- apply effect atomically (upsert/merge/increment)
apply_effect(...)
insert into idempotency_keys(consumer, op_id, applied_at)
values(:c,:id, now)
end if
-- record reading progress (offset/position)
upsert offsets set pos=:pos where consumer=:c commit
2) Optimistic Concurrency (versão do aparelho)
Protege contra o efeito duplo nas corridas:sql update account set balance = balance +:delta,
version = version + 1 where id=:account_id and version=:expected_version;
-- if 0 rows are updated → retry/conflict
3) Sinks Idempotentes (upsert/merge)
Operação «faturar uma vez»:sql insert into bonuses(user_id, op_id, amount)
values(:u,:op,:amt)
on conflict (user_id, op_id) do nothing;
Idempotidade nos protocolos
HTTP/REST
Título 'Idempotency-Key: <uuid' hash> '.
O servidor armazena a gravação da chave e devolve novamente a mesma resposta (ou o código '409 '/' 422' no conflito de invariantes).
Para os «inseguros» POST - obrigatório 'Idempotency-Key' + timeout/política de retração sustentável.
gRPC/RPC
Metadados 'idempotency _ key', 'request _ id' + deadline.
Implementação de servidor - como no REST: tabela de dedução na transação.
Corretores/streaming (Kafka/NATS/Pulsar)
Produtor: estável 'event _ id '/idumpotente (onde suportado).
Consumer: deadup por '(consumer _ id, event _ id)' e/ou pela versão empresarial do aparelho.
DLQ separado para mensagens não identificadas/danificadas.
Webhooks e parceiros externos
Exija 'Idempotency-Key '/' event _ id' no contrato; Uma nova entrega deve ser segura.
Guarde 'notification _ id' e os estados de envio; na retração - não duplique.
Projeto de chaves
Determinação: Retrações devem enviar a mesma chave (gere antecipação no cliente/orquestrador).
Área de visibilidade: Forma 'op _ id' como 'service: aggregate: id: purpose'.
Conflitantes: use UUIDv7/ULID ou hash de parâmetros de negócios (com sal, se necessário).
Hierarquia: geral 'operation _ id' na frente → é transmitido para todas as posições (cadeia idumpotente).
UX e aspectos de alimentos
Uma nova solicitação de chave deve retornar o mesmo resultado (incluindo corpo/status) ou um «já concluído» explícito.
Mostre ao usuário os estados de «operação processada/concluída» em vez de tentar novamente «sorte».
Para operações de longa duração, a chave é polling ('GET/operações/se por causa de').
Observabilidade
Logue 'op _ id', 'event _ id', 'trace _ id', o desfecho é 'APPLIED '/' ALREADY _ APPLIED'.
Métricas: proporção de repetições, tamanho de tabelas de dedução, tempo de transações, conflitos de versões, taxa de DLQ.
A chave deve passar pelo comando → evento → projeção → chamada externa.
Segurança e Complacência
Não guarde PII nas chaves; chave - ID, não payload.
Criptografe os campos sensíveis nos registros de dedução com TTL de longa duração.
Política de armazenamento: TTL e arquivos; direito ao esquecimento - através de cripto-apagar respostas/metadados (se eles contêm PII).
Testes
1. Duplicado: uma mensagem/solicitação de 2 a 5 vezes - o efeito é exatamente uma.
2. Queda entre os passos: antes/depois da gravação do efeito, antes/depois da fixação do ofset.
3. Restart/rewalance dos consumidores: não há dupla aplicação.
4. Concorrência: solicitações paralelas com um 'op _ id' → um efeito, o outro é 'ALREADY _ APPLIED/409'.
5. Chaves de longa duração: Verificação do vencimento da TTL e repetições após a recuperação.
Antipattern
Nova chave aleatória para cada retrai, o sistema não reconhece as repetições.
Dois grupos individuais, primeiro o efeito, depois o efeito ofset - a queda entre eles duplica o efeito.
A confiança é apenas para o corretor, a falta de dedução em sinca/unidade.
Não há uma versão do aparelho: um novo evento muda de estado pela segunda vez.
Fat keys: a chave inclui campos de negócios/PII → vazamentos e índices complexos.
Sem respostas repetitivas, o cliente não pode retocar de forma segura.
Exemplos
Post de pagamento
Cliente: 'POST/payments' + 'Idempotency-Key: k-789'.
Servidor: transação - Cria 'payment' e uma gravação em 'idempotency _ keys'.
Repetição: devolve o mesmo '201 '/corpo; no conflito do invariante, '409'.
Pagamento de bónus (sink)
sql insert into credits(user_id, op_id, amount, created_at)
values(:u,:op,:amt, now)
on conflict (user_id, op_id) do nothing;
Projeção a partir de eventos
O Consumer armazena 'seen' (event _ id) 'e' version 'do aparelho; repetição - upsert ignorado/idimpotente.
O progresso da leitura é registrado na mesma transação que a atualização da projeção.
Folha de cheque da produção
- Todas as operações não seguras definiram a chave Idumpotent e sua área de visibilidade.
- Há tabelas de dedução com TTL e índices exclusivos.
- O efeito e o progresso da leitura são atômicos.
- O modelo write inclui uma competição otimista (versão/sequence).
- Os contratos de API registram 'Idempotency-Key '/' operation _ id' e comportamento de repetição.
- As métricas e os logs contêm 'op _ id '/' event _ id '/' trace _ id'.
- Testes de duplicação, queda e corrida - em CI.
- A política TTL/arquivo e a segurança do PII foram cumpridas.
FAQ
Em que 'Idempotency-Key' é diferente de 'Request-Id'?
'Request-Id' - rastreamento; «Idempotency-Key» é o ID semântico da operação, obrigatório igual quando repetido.
É possível fazer idempotidade sem base de dados?
Para uma janela curta, sim (Redis/kesh intraprocessado), mas sem transação compartilhada, o risco de duplicação aumenta. Em domínios críticos, melhor em transações de base de dados.
O que fazer com os parceiros externos?
Negocie as chaves e as respostas repetidas. Se o parceiro não suporta, inverte a chamada para sua camada idumpotente e guarde «já aplicado».
Como escolher a TTL?
Some os atrasos máximos de retenção do logs + worst-case rede/rebalance + tampão. Adicione o estoque (x 2).
Resultado
Idempotidade é uma disciplina de chaves, transações e versões. Identificadores de operações robustos + atômico fixação do efeito e progresso da leitura + sinks/projeções idumpotentes produzem «exatamente um efeito» sem a magia do nível de transporte. Tornem as chaves determinadas, as TTL realistas e os testes maliciosos. Então os retais e duplicados serão uma rotina, não incidentes.