Idempotencia y llaves
¿Qué es la idempotencia?
Idempotencia: propiedad de operación en la que una repetición con el mismo identificador no cambia el efecto final. En sistemas distribuidos, esta es la forma principal de hacer que el resultado sea equivalente a «exactamente un procesamiento», a pesar de retraídas, mensajes duplicados y timeouts.
Idea clave: cada operación potencialmente repetible debe ser marcada con una clave por la cual el sistema reconoce «ya lo ha hecho» y aplica el resultado no más de una vez.
Donde
Pagos y balances: cargos/abonos por 'operation _ id'.
Reservas/cuotas/límites: la misma ranura/recurso.
Webhooks/notificaciones: la entrega repetida no debe duplicar el efecto.
Importar/migrar: volver a ejecutar archivos/paquetes.
Procesamiento de streaming: tomas del bróker/CDC.
Vistas de claves y ámbito
1. Llave de operación: identificador de intento específico de la operación empresarial
Ejemplos: 'idempotency _ key' (HTTP), 'operation _ id' (RPC).
Área: servicio/unidad; se almacena en una tabla de deduplicación.
2. Clave de evento: identificador único de evento/mensaje
Ejemplos: 'event _ id' (UUID), '(producer_id, sequence)'.
Ámbito: consumidor/grupo de consumidores; protege las proyecciones.
3. Business key es la clave natural de la materia
Ejemplos: 'payment _ id', 'invoice _ number', '(user_id, día)'.
Ámbito: agregado; se aplica en verificaciones de unicidad/versión.
Política de retención y TTL
Llaves TTL ≥ posible ventana de repetición: retén de registro + retardo de red/proceso.
Para dominios críticos (pagos) TTL: días/semanas; para telemetría - reloj.
Limpie las tablas de dedoop de fondo con jobs; para auditoría: archive.
Almacenes para claves (deduplicación)
Transaccional DB (recomendado): índices confiables de upsert/unique, transacción conjunta con efecto.
KV/Redis: rápido, conveniente para un TTL corto, pero sin una transacción conjunta con OLTP - cuidado.
State store stream procesador: local + chenjlog en el corredor; bien en Flink/KStreams.
- idempotency_keys
`consumer_id` (или `service`), `op_id` (PK на пару), `applied_at`, `ttl_expires_at`, `result_hash`/`response_status` (опц.) .
Índices: '(consumer_id, op_id)' - único.
Técnicas básicas de implementación
1) Transacción «efecto + progreso»
Escribir el resultado y confirmar el progreso de la lectura/posición - en una sola transacción.
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 (versión de la unidad)
Protege contra el doble efecto durante las carreras: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)
Operación «acumular una vez»:sql insert into bonuses(user_id, op_id, amount)
values(:u,:op,:amt)
on conflict (user_id, op_id) do nothing;
Idempotencia en los protocolos
HTTP/REST
Título 'Idempotency-Key: <uuid' hash> '.
El servidor almacena el registro de claves y vuelve a devolver la misma respuesta (o código '409 '/' 422' en un conflicto de invariantes).
Para los «inseguros» POST - obligatorio 'Idempotency-Key' + timaut sostenible/política retray.
gRPC/RPC
Los metadatos 'idempotency _ key', 'request _ id' + deadline.
Implementación del servidor - como en NAT: tabla de dedup en una transacción.
Corredores/streaming (Kafka/NATS/Pulsar)
Productor: estable 'event _ id '/productor idempotente (donde se mantiene).
Consumer: dedoop por '(consumer_id, event_id)' y/o por la versión de negocio de la unidad.
DLQ separado para mensajes no idempotentes/dañados.
Webhooks y socios externos
Requiera 'Idempotency-Key '/' event _ id' en el contrato; La entrega repetida debe ser segura.
Almacene 'notification _ id' y los estados de envío; Con Retray - No duplique.
Diseño de claves
Determinismo: los retraídos deben enviar la misma clave (generar por adelantado en el cliente/orquestador).
Ámbito de visibilidad: forme 'op _ id' como 'service: aggregate: id: purpose'.
Colisiones: utilice UUIDv7/ULID o hash de los parámetros de negocio (con sal si es necesario).
Jerarquía: el 'operation _ id' general en el frente → se transmite a todas las suboperaciones (cadena idempotente).
Aspectos de productos y UX
Una solicitud de clave repetida debe devolver el mismo resultado (incluido el cuerpo/estado) o una «ya ejecutada» explícita.
Muestre al usuario los estados de «operación procesada/completada» en lugar de volver a intentarlo «a suerte».
Para operaciones largas, polling por clave ('GET/operations/{ op _ id}').
Observabilidad
Lógica 'op _ id', 'event _ id', 'trace _ id', resultado: 'APPLIED '/' ALEADY _ APPLIED'.
Métricas: porcentaje de repeticiones, tamaño de las tablas de dedoop, tiempo de transacción, conflictos de versiones, tasa DLQ.
Trais: la clave debe pasar por el comando → evento → proyección → llamada externa.
Seguridad y cumplimiento
No almacene la PII en claves; clave - ID, no payload.
Cifre los campos sensibles en las entradas de Dedup con TTL de larga duración.
Política de retención: TTL y archivos; el derecho al olvido - a través de la encriptación de respuestas/metadatos (si contienen PII).
Pruebas
1. Duplicados: ejecución de un solo mensaje/consulta 2-5 veces - efecto exactamente uno.
2. Caída entre pasos: antes/después de registrar el efecto, antes/después de fijar el offset.
3. Restart/reequilibrio de los consumidores: no hay doble uso.
4. Competencia: consultas paralelas con un 'op _ id' → un efecto, la segunda es 'ALEADY _ APPLIED/409'.
5. Claves de larga vida: comprueba la caducidad de la TTL y las repeticiones después de la recuperación.
antipatterny
Una nueva clave aleatoria para cada retiro: el sistema no reconoce repeticiones.
Dos commitas separadas: primero el efecto, luego el offset - la caída entre ellos duplica el efecto.
Confianza sólo para el corredor: no hay dedoop en el azul/unidad.
No hay versión del agregado: el evento de repetición cambia el estado por segunda vez.
Fat keys: la clave incluye campos de negocio/PII → fugas e índices complejos.
No hay respuestas repetibles: el cliente no puede retractarse con seguridad.
Ejemplos
POST de pago
Cliente: 'POST/payments' + 'Idempotency-Key: k-789'.
Servidor: transacción: crea un 'pago' y una entrada en 'idempotency _ keys'.
Repetición: devuelve el mismo '201 '/cuerpo; en conflicto invariante - '409'.
Acumulación de bonificación (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;
Proyección de eventos
Consumer almacena 'seen (event_id)' y 'version' de la unidad; repetición - upsert ignorado/idempotente.
El progreso de la lectura se registra en la misma transacción que la actualización de proyección.
Lista de comprobación de producción
- Se ha definido una clave idempotente y su área de visibilidad para todas las operaciones inseguras.
- Hay tablas de dedoop con TTL e índices únicos.
- El efecto y el progreso de la lectura se combinan atomicamente.
- El modelo escrito incluye una competencia optimista (versión/sequence).
- Los contratos API registran 'Idempotency-Key '/' operation _ id' y el comportamiento de las repeticiones.
- Métricas y registros contienen 'op _ id '/' event _ id '/' trace _ id'.
- Pruebas de duplicados, caídas y carreras - en CI.
- Se han respetado las políticas de TTL/archivo y seguridad PII.
FAQ
¿En qué se diferencia 'Idempotency-Key' de 'Request-Id'?
'Request-Id' - rastreo; «Idempotency-Key» es el identificador semántico de la operación, obligatorio para las repeticiones.
¿Se puede hacer idempotencia sin DB?
Para una ventana corta, sí (caché Redis/intraprocesado), pero sin una transacción conjunta, el riesgo de tomas aumenta. En dominios críticos, mejor en una sola transacción de BD.
¿Qué hacer con los socios externos?
Negocie las claves y las respuestas repetibles. Si el socio no es compatible, envuelva la llamada en su capa idempotente y almacene el «ya aplicado».
¿Cómo elegir TTL?
Resume los retardos máximos: retén de registro + caso de trabajo de red/rebalance + búfer. Agregue el inventario (× 2).
Resultado
La idempotencia es la disciplina de claves, transacciones y versiones. Los identificadores de operación constantes + la fijación atómica del efecto y el progreso de lectura + sinks/proyecciones idempotentes producen «exactamente un efecto» sin la magia del nivel de transporte. Haga que las claves sean deterministas, que el TTL sea realista y que las pruebas sean malintencionadas. Entonces los retraídos y duplicados se convertirán en una rutina, no en incidentes.