Carlos Bautista

Distributed Systems · 15 de diciembre de 2025 · 3 min de lectura

Idempotencia en sistemas distribuidos: x-request-id, Redis y reprocesamiento de eventos

Cuando un consumidor reintenta o un evento se reprocesa, la misma operación puede ejecutarse varias veces. Un identificador de petición y una verificación previa en caché evitan los duplicados sin cargar la base transaccional.

Carlos Andrés Bautista PolaníaMicroservices · Event-Driven · Idempotency · Redis · Kafka · RabbitMQ · API Design

En un sistema distribuido, casi nada se ejecuta exactamente una vez. Un consumidor de eventos reintenta tras un timeout, un cliente reenvía una petición que creía perdida, un despliegue reprocesa una parte de la cola. El diseño tiene que asumir que la misma operación llegará más de una vez.

Dónde aparece la duplicidad

Con Kafka, RabbitMQ o SQS, la entrega "al menos una vez" es la norma. Los consumidores reintentan ante fallos transitorios, y un POST o un PUT puede llegar a ejecutarse varias veces: se crea el mismo registro dos veces, se cobra dos veces, se disparan dos notificaciones. El resultado son duplicados e inconsistencias que después cuesta rastrear.

La idea: idempotencia

Una operación es idempotente si ejecutarla varias veces produce el mismo efecto final que ejecutarla una sola vez. No se trata de evitar que el mensaje llegue repetido —eso no se puede garantizar—, sino de que el segundo intento no cambie nada.

Un identificador por operación

La clave no está solo en el broker —ninguno garantiza entrega única—, sino en poder identificar cada operación. El punto de partida es que cada una traiga un identificador estable: un x-request-id que genera el cliente, o el message-id que ya asigna el broker. Ese identificador es el mismo en el primer intento y en el reintento, y es lo que permite reconocer que "esto ya lo vi".

Verificación previa en caché

Antes de tocar la lógica de negocio, el servicio comprueba si ese identificador ya fue procesado. Esa verificación vive en un caché efímero —Redis—, no en la base transaccional:

recibir operación con request_id
si request_id ya está registrado en el caché:
    ignorar: la operación ya se procesó
si no:
    procesar la operación
    registrar request_id en el caché

El lookup en Redis es O(1) y se resuelve en memoria, así que la verificación no supone una consulta a la base de datos transaccional en cada evento. Y como el chequeo ocurre antes de la lógica de negocio, un reprocesamiento tampoco genera transacciones repetidas: se quita presión del componente más caro de escalar.

Desacople del dominio

La lógica de idempotencia no pertenece al dominio. El servicio no debería estar lleno de comprobaciones "¿ya hice esto?" mezcladas con las reglas de negocio. Mantenerla en una capa previa —un middleware, un decorador o un paso explícito en el consumidor— deja el dominio limpio y hace que la protección sea consistente en todas las operaciones que la necesitan.

Consideraciones adicionales

El patrón anterior —identificador único más verificación en caché— es el núcleo de la solución. A partir de ahí, un sistema en producción añade decisiones que este planteamiento no aborda: durante cuánto tiempo retener cada identificador en el caché, qué hacer con los mensajes que fallan de forma persistente, y hasta dónde llevar las garantías de entrega. Son extensiones razonables, pero cada una es una decisión de diseño con su propio costo y queda para un análisis aparte.

Conclusión

En un sistema distribuido, donde los mensajes se reintentan, la idempotencia no es un lujo. Un identificador único por operación y una verificación previa en un caché rápido son un patrón simple y robusto para garantizarla: protegen la base transaccional y mantienen el dominio enfocado en lo que le corresponde.


Publicado originalmente en LinkedIn.