Idempotência: operações seguras de repetir
1. Fundamentos da Idempotência
1.1 Definição formal
Idempotência é a propriedade de uma operação produzir o mesmo resultado independentemente do número de vezes que é executada. Formalmente, uma função f é idempotente se f(f(x)) = f(x). Em sistemas de software, isso significa que executar uma requisição uma vez ou múltiplas vezes produz o mesmo efeito colateral observável.
1.2 Diferença entre idempotência e segurança
Em APIs REST, "segurança" (safe methods) refere-se a operações que não alteram estado (GET, HEAD, OPTIONS). Idempotência é diferente: uma operação pode alterar estado, mas deve fazê-lo de forma que repetições não causem efeitos adicionais. DELETE é idempotente (após a primeira exclusão, repetições retornam o mesmo resultado), mas não é seguro (altera estado).
1.3 Exemplos clássicos
GET /users/123 → Idempotente e seguro
PUT /users/123 → Idempotente (substitui recurso)
DELETE /users/123 → Idempotente (remove recurso)
POST /users → Não idempotente (cria novo recurso)
2. Por que Idempotência é Crucial em Arquiteturas Distribuídas
2.1 Problemas de redes não confiáveis
Em redes distribuídas, timeouts e retransmissões são inevitáveis. Um cliente pode enviar uma requisição, o servidor processá-la com sucesso, mas a resposta se perder. O cliente reenvia a requisição. Sem idempotência, isso pode causar duplicação de pedidos, cobranças ou inconsistências.
2.2 Garantia de consistência em escalabilidade horizontal
Com múltiplas instâncias de serviço, uma requisição pode ser roteada para diferentes servidores em tentativas sucessivas. Idempotência garante que, independentemente de qual instância processa a requisição, o estado final seja consistente.
2.3 Impacto em filas de mensagens
Sistemas de mensageria frequentemente usam entrega "at-least-once" (pelo menos uma vez). Sem idempotência, isso se traduz em processamento duplicado. Idempotência permite tratar "at-least-once" como "exactly-once" na prática.
# Exemplo: processamento de pagamento com idempotência
{
"idempotency_key": "pagamento-2024-01-abc123",
"amount": 100.00,
"account": "user-456"
}
3. Padrões de Implementação de Idempotência
3.1 Chave de idempotência (Idempotency Key)
O cliente gera um identificador único (UUID) e o envia com a requisição. O servidor armazena o resultado associado à chave. Requisições duplicadas com a mesma chave retornam o resultado original.
# Armazenamento em Redis com TTL
SET idempotency:pagamento-2024-01-abc123
'{"status": "processed", "transaction_id": "txn-789"}'
EX 86400 # Expira em 24 horas
3.2 Idempotência baseada em estado
Antes de executar uma operação, verifica-se se o resultado já existe. Se sim, retorna-o sem executar novamente.
# Verificação de estado antes da execução
function createUser(email, name) {
user = db.users.findByEmail(email)
if (user) return user // Já existe, retorna existente
return db.users.insert({email, name})
}
3.3 Idempotência baseada em versão (Optimistic Locking)
Usa um campo de versão ou timestamp para garantir que apenas uma atualização seja aplicada.
UPDATE accounts
SET balance = balance - 100, version = version + 1
WHERE id = 123 AND version = 5
-- Se version != 5, a atualização não ocorre (conflito)
4. Idempotência em APIs REST
4.1 Métodos HTTP idempotentes
GET /users/123 → Sempre retorna o mesmo recurso
PUT /users/123 → Substitui recurso, múltiplas execuções = mesmo estado
DELETE /users/123 → Após primeira exclusão, retorna 404 ou 200
PATCH /users/123 → Idempotente se usar operações absolutas (ex: set)
4.2 Implementando POST idempotente com cabeçalho Idempotency-Key
POST /api/payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{
"amount": 250.00,
"currency": "BRL"
}
# Resposta original (primeira execução):
HTTP 201 Created
{
"payment_id": "pay_abc123",
"status": "confirmed"
}
# Resposta para requisição duplicada:
HTTP 200 OK # Ou 409 Conflict, dependendo da implementação
{
"payment_id": "pay_abc123",
"status": "confirmed"
}
4.3 Tratamento de respostas duplicadas
O servidor deve retornar o mesmo resultado da primeira execução, não executar novamente a operação. Isso exige armazenamento temporário dos resultados.
5. Idempotência em Operações de Banco de Dados
5.1 UPSERT como operação idempotente
INSERT INTO orders (order_id, status, amount)
VALUES ('ord-123', 'confirmed', 500.00)
ON CONFLICT (order_id)
DO UPDATE SET status = EXCLUDED.status;
-- Se já existe, atualiza para o mesmo valor (idempotente)
5.2 Transações atômicas e compare-and-swap
BEGIN;
SELECT balance FROM accounts WHERE id = 123 FOR UPDATE;
-- Verifica saldo suficiente
UPDATE accounts
SET balance = balance - 100
WHERE id = 123 AND balance >= 100;
COMMIT;
-- Se balance < 100, nenhuma linha é afetada (idempotente)
5.3 Idempotência em sharding
Em bancos sharded, a chave de idempotência deve incluir o shard key para garantir que requisições duplicadas sejam roteadas para o mesmo shard.
6. Idempotência em Sistemas de Mensageria e Eventos
6.1 Deduplicação de mensagens
# Tabela de deduplicação
CREATE TABLE processed_messages (
message_id VARCHAR(64) PRIMARY KEY,
processed_at TIMESTAMP DEFAULT NOW()
);
# Processamento com verificação
INSERT INTO processed_messages (message_id) VALUES ('msg-456')
ON CONFLICT (message_id) DO NOTHING;
-- Se já existe, a mensagem já foi processada
6.2 Processamento idempotente em Kafka
No Kafka, cada mensagem tem um offset único. O consumidor pode armazenar offsets processados e usar a chave da mensagem para deduplicação.
6.3 Sagas e compensações idempotentes
Operações de rollback em sagas devem ser idempotentes para lidar com falhas durante a compensação.
# Compensação idempotente
function cancelOrder(orderId) {
order = db.orders.findById(orderId)
if (order.status == 'cancelled') return // Já cancelado
db.orders.update(orderId, {status: 'cancelled'})
db.payments.refund(orderId) // Deve ser idempotente
}
7. Monitoramento e Validação de Idempotência
7.1 Métricas importantes
- Taxa de duplicatas detectadas:
duplicates_detected / total_requests - Tempo de vida de chaves de idempotência: média e percentis
- Latência de verificação de idempotência
7.2 Logs e traces
# Log de requisição duplicada
2024-01-15 10:30:45 [WARN] Duplicate request detected
idempotency_key: "550e8400-e29b-41d4-a716-446655440000"
original_response: {"payment_id": "pay_abc123", "status": "confirmed"}
returned cached result
7.3 Testes de idempotência
Estratégias de teste incluem replay de requisições, simulação de timeouts e testes de concorrência.
8. Armadilhas e Boas Práticas
8.1 Efeitos colaterais não idempotentes
Cuidado com logs incrementais, notificações por email e cobranças. Se uma operação for duplicada, esses efeitos colaterais também devem ser idempotentes.
8.2 Limpeza de chaves de idempotência
Use TTL adequado (geralmente 24-48 horas) e implemente garbage collection para chaves expiradas.
8.3 Decisão arquitetural
- Idempotência no cliente: cliente gera e gerencia chaves
- Idempotência no servidor: servidor extrai chave da requisição
- Idempotência em middleware: gateway ou proxy gerencia idempotência
A escolha depende do controle que se tem sobre os clientes e da criticidade da consistência.
Referências
- Idempotência na documentação do Stripe API — Documentação oficial do Stripe sobre implementação de idempotência em APIs financeiras
- RFC 7231 - HTTP/1.1 Semantics and Content — Especificação oficial que define métodos idempotentes e seguros no protocolo HTTP
- Idempotency Patterns in Distributed Systems — Martin Fowler sobre padrões de idempotência em sistemas distribuídos
- AWS: Idempotent API Design — Guia da AWS para projetar APIs idempotentes com API Gateway
- Kafka Exactly-Once Semantics — Artigo da Confluent sobre processamento exatamente-uma-vez em Kafka