idempotencia en kafka

introducción

La idempotencia evita ejecutar dos veces el mismo evento cuando hay reintentos, timeouts o fallos de red. En Kafka, el producer idempotente garantiza “no duplicates on the broker” por partición.

Para flujos de dinero o inventario, duplicar un mensaje puede ser fatal (pagar dos veces, descontar stock de más). Activar idempotencia es barato y suele bastar junto con buenas prácticas de consumo.

idea clave: enable.idempotence = true hace que el broker ignore reenvíos del mismo mensaje de un producer gracias a un identificador de productor y secuencia por partición.
¿Cuál prefieres? Producer sin idempotencia vs con idempotencia
idempotencia activada ⇒ pago único ✅ · desactivada ⇒ riesgo de doble pago ❌.

¿cómo se generan duplicados?

El productor envía, ocurre timeout o se pierde el ACK → reintenta → el broker recibe otra vez el mismo mensaje y lo persiste de nuevo.

Flujo sin idempotencia generando duplicado por reintento
sin idempotencia: los reintentos pueden introducir duplicados.
Mismo evento procesado dos veces por el consumidor
el consumidor termina procesando 2 veces el mismo evento.

¿cómo lo evita kafka?

Con idempotencia, el producer obtiene un Producer ID y asigna un número de secuencia; el broker guarda el último secuencial aceptado y descarta duplicados.

Flujo con idempotencia: reintento detectado y descartado
con idempotencia: el broker detecta la secuencia duplicada y la ignora.

ejemplo práctico: producer idempotente

Config mínima (Java):

Properties p = new Properties();
p.put("bootstrap.servers", "<brokers>");
p.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
p.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

// idempotencia
p.put("enable.idempotence", "true");
p.put("acks", "all");                             // requerir confirmación de réplicas
p.put("retries", Integer.toString(Integer.MAX_VALUE));
p.put("max.in.flight.requests.per.connection", "5"); // OK con Kafka >= 1.1

KafkaProducer<String, String> producer = new KafkaProducer<>(p);

Para exactly-once entre varios tópicos/particiones y con escritura a estado externo, usa transacciones:

p.put("transactional.id", "payments-tx-01");  // id único por instancia
KafkaProducer<String, String> txProducer = new KafkaProducer<>(p);
txProducer.initTransactions();

try {
  txProducer.beginTransaction();
  txProducer.send(new ProducerRecord<>("pagos", key, value));
  // ... (otros topics / writes compatibles)
  txProducer.commitTransaction();
} catch (Exception e) {
  txProducer.abortTransaction();
}

demo: idempotencia y reintentos

envía un pago y, con timeout activo, observa el reintento. Con idempotencia=ON el broker ignora el duplicado; con OFFel consumidor termina procesando dos pagos.

kafka producer
idempotencia y reintentos
seq actual: 0
broker / topic
detección de duplicados por secuencia
almacenados: 0ignorados: 0total en rail: 0
consumer
procesa sólo eventos almacenados
índice leído: 0/0total procesado: $0
interacción — secuencia del producer, detección de duplicados y total consumido.

patrón de consumo: defensa adicional

Aunque el broker deduplica, es buena práctica que el consumidor tenga una llave idempotente (por ejemplo, eventoId) y registre qué ya procesó, sobre todo si llama APIs o DBs.

// pseudocódigo
if (alreadyProcessed(eventoId)) return;
process(event);
markProcessed(eventoId);
¿Qué es la idempotencia en Event Driven?
resumen conceptual.
enable.idempotence = true
regla de oro del producer.

checklist rápido

  • enable.idempotence=true, acks=all, retries=∞.
  • max.in.flight.requests.per.connection ≤ 5 (Kafka >= 1.1).
  • usa transactional.id si necesitas transacciones entre topics/DB.
  • define clave idempotente de negocio y de-duplicación en el consumidor.
  • observabilidad: métricas de reintentos, error rate y DLQ si aplica.