01Garantías de entrega — en sistemas distribuidos, "enviar" ≠ "entregar"
Garantía
¿Duplicados posibles?
¿Pérdidas posibles?
Cuándo usarla
At-most-once
No. El mensaje se envía una sola vez y no se reintenta.
Sí — puede perderse
Métricas, logs de telemetría donde perder un dato ocasional es aceptable. Máximo rendimiento.
At-least-once
Sí — puede duplicarse
No. El broker reintenta hasta confirmar (ack) la entrega.
La garantía más común en producción. Requiere que el consumidor sea idempotente para manejar duplicados.
Exactly-once
No. El broker deduplica antes de entregar.
No. Garantía fuerte de entrega.
Muy costoso en rendimiento. Kafka lo soporta con transacciones + idempotent producer. En la práctica, se simula con idempotencia en el consumidor.
💡
La verdad sobre "Exactly-once"
Exactly-once real a nivel de red es imposible — siempre existe el riesgo de un fallo entre el procesamiento y el ack. Lo que Kafka llama "exactly-once" es exactly-once semántico: el mensaje puede llegar dos veces, pero el efecto en la BD es el mismo que si llegara una. Eso se llama idempotencia. En la práctica, at-least-once + consumidor idempotente = exactly-once semántico.
02Idempotencia — la pieza maestra del consumidor resiliente
✅
CONCEPTO CORE
Idempotencia
f(f(x)) = f(x)Seguro para reintentosIdempotency Key
Una operación es idempotente si ejecutarla N veces produce el mismo resultado que ejecutarla 1 vez. Es la propiedad que hace que los reintentos automáticos sean seguros en sistemas distribuidos.
Operaciones idempotentes naturales: PUT (actualizar un recurso completo), DELETE (borrar algo ya borrado = no pasa nada).
No idempotentes por defecto: POST (crear), operaciones con sumas/incrementos (balance += 100) — requieren implementación explícita.
La forma más simple de implementar idempotencia: guardar el ID del mensaje procesado en una tabla. Antes de procesar, verificar si ya existe. Si existe, saltar. Si no, procesar y guardar.
Implementación de idempotencia con tabla de deduplicaciónJAVA
@Transactional
public void handlePaymentProcessed(PaymentEvent event) {
// 1. ¿Ya procesamos este evento?
if (processedEvents.existsById(event.eventId())) {
log.warn("Duplicate event {}, skipping", event.eventId());
return; // idempotente — no hacer nada
}
// 2. Procesar el evento
orderService.markAsPaid(event.orderId(), event.amount());
// 3. Marcar como procesado — misma TX
processedEvents.save(new ProcessedEvent(
event.eventId(),
Instant.now()
));
// Si falla aquí, la TX hace rollback.
// El retry procesará el evento de nuevo — seguro.
}
03Transactional Outbox — resolviendo la doble escritura
El problema de la doble escritura — y su solución
❌ EL PROBLEMA — Dos escrituras, dos posibles fallos
1. save(order) → PostgreSQL ✅
→
2. publish(event) → Kafka 💥 FALLA
→
Estado inconsistente: BD actualizada, evento perdido
✅ LA SOLUCIÓN — Transactional Outbox
MISMA TRANSACCIÓN ↓
1. save(order) → tabla orders
2. save(event) → tabla outbox
Si falla → rollback de ambas. Nunca inconsistente.
→
RELAY / PUBLISHER
3. Lee tabla outbox
4. Publica a Kafka
5. Marca como enviado
Si falla → reintenta. Kafka recibe at-least-once.
→
CONSUMIDOR
6. Recibe evento
7. Procesa (idempotente)
La idempotencia maneja duplicados del relay.
Implementaciones del Relay: Debezium (CDC — lee el WAL/binlog de Postgres/MySQL, no requiere tabla explícita), polling propio (más simple, posible lag), o frameworks como Eventuate. Outbox + Idempotencia es el combo que garantiza consistencia eventual entre microservicios sin transacciones distribuidas (2PC).
04Dead Letter Queue y Backpressure — manejar la presión y los mensajes venenosos
☠️
PATRÓN
Dead Letter Queue (DLQ)
Mensaje venenosoAislamientoAuditoría
Cola especial donde van a parar los mensajes que fallaron repetidamente y no pueden ser procesados. Evita que un mensaje "venenoso" bloquee el flujo principal indefinidamente.
Flujo: Mensaje falla → reintento 1 → reintento 2 → reintento 3 → → DLQ
En la DLQ: El equipo puede inspeccionar el mensaje, entender por qué falló (bug, datos corruptos, schema inválido), corregir el problema y re-publicarlo al topic principal.
Sin DLQ, un mensaje corrupto puede bloquear toda la partición de Kafka si el consumidor no puede avanzar. La DLQ es la válvula de seguridad que libera el flujo principal.
🌊
PATRÓN
Backpressure & Asincronía
Cola de mensajesDesacoplamientoFlujo controlado
Backpressure es el mecanismo por el cual el consumidor señaliza al productor que está saturado para que baje el ritmo. Las colas de mensajes actúan como buffer que absorbe los picos de carga.
Sin cola (síncrono): Servicio A llama a B → si B está lento, A espera → A se satura → cascada de fallos.
Con cola (asíncrono): A publica en la cola → B consume a su ritmo → la cola absorbe los picos → A nunca espera → ambos sobreviven los picos.
La asincronía con colas cambia la garantía: ya no es "B procesó la solicitud" sino "la solicitud fue encolada correctamente". Si el negocio requiere respuesta síncrona, usa async + polling o callbacks.
05Tabla resumen — keyword → patrón correcto
Si el entrevistador dice…
Patrón
Por qué
Herramienta / impl.
"cascada de fallos", "servicio B lento hunde A"
Circuit Breaker
Corta las llamadas cuando B falla. A responde con fallback en vez de bloquearse.
Resilience4j · Polly · Istio
"todos reintentan a la vez", "thundering herd"
Retry + Jitter
El jitter dispersa los reintentos. Sin él, el servicio caído recibe una avalancha sincronizada.
Exponential Backoff + Full Jitter
"reintentos duplican registros", "at-least-once"
Idempotencia
El consumidor detecta y descarta duplicados. El resultado es el mismo si llega 1 o 100 veces.
Tabla de eventos procesados + UUID
"save en BD pero falla el publish a Kafka"
Transactional Outbox
Guarda el evento en la misma TX de BD. Un relay lo publica de forma garantizada y separada.
Debezium · Eventuate · Polling propio
"mensaje que siempre falla bloquea la cola"
Dead Letter Queue
Aísla mensajes venenosos sin detener el flujo principal. Permite análisis y re-proceso posterior.
SQS DLQ · Kafka · RabbitMQ
"producer más rápido que consumer", "saturación"
Backpressure / Cola
La cola actúa de buffer. El consumidor procesa a su ritmo sin que el productor lo sature.
Kafka · RabbitMQ · SQS · Project Reactor
"fallo en un módulo hunde todo el servicio"
Bulkhead
Pools de threads separados por módulo. El fallo de uno no agota los recursos de los demás.
Resilience4j Bulkhead · Semaphore