Arquitectura orientada a eventos: qué pasa cuando falla un consumidor
Un cliente crea un pedido y la API responde que la operación fue exitosa. El servicio de pedidos guardó la información y publicó un evento para que inventario descuente existencias y notificaciones envíe un correo. Unos segundos después, el consumidor de inventario falla. El pedido existe, pero el stock todavía no refleja esa operación.
Este escenario no es una excepción en una arquitectura orientada a eventos: es una situación que debe formar parte del diseño. El productor puede haber terminado correctamente su trabajo mientras uno o varios consumidores fallan, se retrasan, procesan dos veces el mismo evento o dejan de estar disponibles.
En este artículo veremos cómo razonar sobre la entrega de eventos, los reintentos, la idempotencia, las colas de mensajes no procesables, la trazabilidad y la consistencia eventual mediante un flujo genérico de pedidos, inventario y notificaciones.
El problema fundamental: el productor y los consumidores tienen ciclos de vida distintos
En un flujo síncrono, una solicitud puede esperar la respuesta de todos los componentes involucrados antes de confirmar el resultado. En un flujo orientado a eventos, el productor suele persistir un cambio y publicar un evento para que otros servicios reaccionen de manera independiente.
Por ejemplo:
- El servicio de pedidos registra el pedido.
- Publica un evento como PedidoCreado.
- El consumidor de inventario intenta reservar o descontar productos.
- El consumidor de notificaciones intenta enviar una confirmación al cliente.
El productor no controla directamente cuándo terminarán los consumidores ni puede asumir que todos procesarán el evento correctamente. Su confirmación puede significar únicamente que el pedido fue guardado, no que el inventario fue actualizado ni que el correo fue enviado.
Por eso, el contrato de la API debe ser claro. Una respuesta exitosa podría indicar que el pedido fue aceptado y persistido, mientras que la reserva de inventario y la notificación se completarán de forma eventual. Si el negocio necesita una confirmación inmediata de stock, quizá ese paso no deba modelarse exclusivamente como una reacción asíncrona.
La pregunta que debe responder el diseño
Antes de elegir una plataforma de mensajería, conviene responder: ¿qué significa que el evento haya sido entregado y qué significa que haya sido procesado?
Recibir un evento no implica necesariamente que el consumidor haya completado su operación. Un consumidor puede leer el mensaje, fallar antes de confirmar su procesamiento y volver a recibirlo. También puede completar la operación y fallar antes de informar que terminó.
Esta diferencia explica por qué los sistemas distribuidos deben diseñarse asumiendo fallos parciales y posibles entregas duplicadas.
Modelar la entrega: no todos los eventos tienen la misma garantía
Las arquitecturas orientadas a eventos suelen describirse mediante distintas garantías de entrega. Estas garantías no son propiedades abstractas: determinan cómo deben implementarse los consumidores y qué riesgos acepta el negocio.
Entrega como máximo una vez
El evento se intenta entregar una vez y no se repite si ocurre un fallo. Puede reducir duplicados, pero existe el riesgo de perder eventos. Este comportamiento puede ser aceptable para métricas no críticas o señales cuyo valor desaparece rápidamente, pero suele ser insuficiente para operaciones como reservar inventario o registrar un pago.
Entrega al menos una vez
El sistema puede entregar el mismo evento más de una vez para evitar perderlo. Es una opción frecuente en sistemas distribuidos porque prioriza la durabilidad del mensaje, pero obliga a que el consumidor sea idempotente.
En el flujo del pedido, el consumidor de inventario podría recibir dos veces el evento PedidoCreado. Si cada recepción descuenta unidades sin verificar el estado anterior, el stock quedaría incorrectamente reducido.
Procesamiento exactamente una vez
“Exactamente una vez” suele ser una afirmación que requiere mucha precisión. Puede referirse a la entrega, al procesamiento dentro de una plataforma concreta o al efecto final sobre una base de datos externa. No debe asumirse que una garantía del sistema de mensajería se extiende automáticamente a todos los efectos de negocio.
Incluso cuando una infraestructura ofrece mecanismos para evitar ciertos duplicados, el consumidor puede fallar al comunicarse con otra base de datos, un servicio externo o un proveedor de correo. Por eso, la idempotencia y la trazabilidad siguen siendo importantes.
Reintentos e idempotencia: cómo evitar que un fallo temporal cause daños
Un consumidor puede fallar por motivos transitorios: una base de datos no está disponible, una dependencia responde lentamente, un despliegue reinicia una instancia o se alcanza un límite de conexiones. En estos casos, reintentar puede ser adecuado.
Sin embargo, reintentar sin límites ni criterios puede empeorar el incidente. Una dependencia caída puede recibir más tráfico, la cola puede acumular mensajes y un evento antiguo puede bloquear el procesamiento de otros.
Diseñar una política de reintentos
Una política razonable debe definir, como mínimo:
- Cuántas veces se reintentará el procesamiento.
- Cuánto tiempo se esperará entre intentos.
- Si el intervalo será fijo o progresivo.
- Qué errores son transitorios y cuáles indican un problema permanente.
- Qué ocurre cuando se agota el número de intentos.
El backoff progresivo puede evitar que todos los consumidores reintenten al mismo tiempo. También puede ser necesario incorporar cierta variación en los intervalos para reducir picos sincronizados, especialmente cuando muchos mensajes fallan por la misma dependencia.
No todos los errores deben reintentarse. Un evento inválido, un producto inexistente o un contrato incompatible probablemente no se corregirán repitiendo el mismo mensaje. En cambio, un timeout frente a una dependencia podría ser transitorio. La clasificación debe reflejar las reglas del dominio y no solo el tipo de excepción técnica.
La idempotencia como propiedad del consumidor
Un consumidor idempotente produce el mismo efecto de negocio aunque procese varias veces el mismo evento. Esto no significa ignorar todos los duplicados: significa reconocerlos y evitar repetir una operación que ya fue aplicada.
Algunas estrategias habituales son:
- Asignar un identificador único a cada evento y registrar los identificadores ya procesados.
- Usar una clave de idempotencia asociada a la operación de negocio.
- Actualizar el estado mediante transiciones explícitas, evitando aplicar dos veces la misma transición.
- Diseñar operaciones con condiciones, como reservar stock solo si la reserva para ese pedido todavía no existe.
La elección depende del caso. Guardar identificadores procesados requiere gestionar su retención y consistencia. Usar únicamente el estado actual puede ser insuficiente si dos eventos llegan fuera de orden. La idempotencia no debe tratarse como una casilla que se marca, sino como una decisión concreta sobre cómo proteger cada efecto.
Qué ocurre cuando el evento no puede procesarse
Después de varios intentos, algunos mensajes seguirán sin poder procesarse. Mantenerlos en el flujo principal indefinidamente puede impedir que otros eventos avancen o generar una carga constante sobre una dependencia defectuosa.
Una dead-letter queue, o cola de mensajes no procesables, permite separar esos eventos para analizarlos sin perderlos silenciosamente. No es un depósito donde el problema desaparece: es un mecanismo operativo para aislarlo y facilitar su recuperación.
Qué información debe conservarse
Para investigar un mensaje fallido, conviene conservar información suficiente para reconstruir el contexto:
- Identificador del evento.
- Tipo y versión del evento.
- Momento de publicación y de cada intento.
- Consumidor que falló.
- Número de reintentos.
- Motivo del último error.
- Identificador de correlación y de la operación de negocio.
También es importante definir quién revisa esa cola, qué criterios se utilizan para reprocesar un mensaje y cómo se evita que una corrección vuelva a generar el mismo fallo. El reprocesamiento debe considerar que el estado del sistema pudo haber cambiado desde el primer intento.
Errores frecuentes con las dead-letter queues
- Crear la cola pero no establecer alertas ni responsables.
- Reprocesar todos los mensajes sin corregir la causa raíz.
- Eliminar mensajes para reducir el volumen sin evaluar el impacto de negocio.
- No distinguir entre un evento inválido y una dependencia temporalmente caída.
- Permitir que los mensajes permanezcan allí indefinidamente sin una política de retención o resolución.
Para algunos procesos críticos, una cola de revisión manual puede ser necesaria. Para otros, puede ser suficiente una reparación automática basada en la causa del fallo. La decisión debe considerar el costo de la inconsistencia y la capacidad operativa del equipo.
Consistencia eventual en el flujo de pedidos
Supongamos que el servicio de pedidos confirmó la creación de un pedido, pero el consumidor de inventario está temporalmente detenido. Durante un período, el pedido aparecerá como creado mientras el inventario aún no refleja la reserva.
Eso es consistencia eventual: distintos componentes pueden observar estados diferentes durante un intervalo, con la expectativa de converger cuando los eventos se procesen correctamente.
Para que esta convergencia sea operable, el dominio necesita estados explícitos. Por ejemplo:
- Pedido creado: el pedido fue persistido.
- Inventario pendiente: la reserva todavía no fue confirmada.
- Inventario reservado: la operación de stock terminó correctamente.
- Inventario rechazado: no hay disponibilidad o la solicitud no es válida.
- Notificación pendiente: el correo o mensaje aún no fue enviado.
- Notificación fallida: se agotaron los intentos definidos.
Estos estados permiten que la API, el equipo de soporte y los procesos automáticos distingan entre una operación completada y una operación pendiente. Ocultar la inconsistencia detrás de una respuesta genérica suele dificultar la recuperación.
¿Qué pasa si inventario falla después de confirmar el pedido?
El negocio debe definir una política, no solo una implementación técnica. Algunas alternativas son:
- Mantener el pedido pendiente hasta completar la reserva.
- Cancelar el pedido si la reserva no se logra dentro de un plazo.
- Permitir el pedido y gestionar posteriormente una falta de stock.
- Solicitar una acción manual para ciertos productos o situaciones.
Ninguna opción es universal. La decisión depende del tipo de producto, las reglas comerciales y el costo de confirmar una operación que después no puede cumplirse.
Cuando varias acciones deben coordinarse sin una transacción única, pueden aparecer patrones como una saga, donde cada paso tiene una acción compensatoria. Por ejemplo, si una reserva posterior falla, el sistema podría cancelar el pedido o liberar una reserva previa. Una compensación no deshace el pasado de forma perfecta; aplica una nueva operación para llevar el dominio a un estado aceptable.
Publicación confiable y trazabilidad de extremo a extremo
Existe otro fallo importante: el servicio de pedidos guarda el pedido en su base de datos, pero falla antes de publicar el evento. Si el evento se construye y publica por separado de la transacción que guarda el pedido, ambas operaciones pueden quedar desincronizadas.
Una alternativa habitual es el patrón Transactional Outbox. El servicio guarda el cambio de negocio y un registro del evento pendiente en la misma transacción local. Después, un proceso separado publica los eventos pendientes y marca su estado de publicación.
Este patrón no elimina todos los problemas. El proceso que publica puede reiniciarse después de publicar y antes de marcar el registro como enviado, por lo que el evento podría publicarse más de una vez. Los consumidores aún necesitan idempotencia. Además, hay que gestionar eventos pendientes, errores de publicación y limpieza de registros.
Qué observar en producción
La observabilidad debe cubrir el recorrido completo, no únicamente el estado del productor. Algunas señales útiles son:
- Edad del evento más antiguo pendiente.
- Tiempo entre la publicación y el procesamiento.
- Cantidad de reintentos por consumidor.
- Mensajes enviados a la dead-letter queue.
- Eventos procesados duplicadamente.
- Duración del procesamiento y tasa de errores.
- Pedidos que permanecen demasiado tiempo en un estado intermedio.
Los identificadores de correlación permiten relacionar la solicitud original, el pedido, el evento y las operaciones de cada consumidor. Sin esa relación, una investigación puede reducirse a buscar mensajes aislados en varios servicios.
Las métricas deben acompañarse de logs estructurados y trazas cuando el contexto lo justifique. También conviene definir alertas basadas en impacto: no es lo mismo tener un reintento aislado que acumular pedidos pendientes de reserva durante un período prolongado.
Errores de diseño que conviene evitar
- Tratar la publicación como si fuera el procesamiento completo: el productor debe comunicar qué garantiza realmente.
- Asumir que cada evento llega una sola vez: los consumidores deben tolerar duplicados si la entrega es al menos una vez.
- Reintentar indefinidamente: los reintentos necesitan límites, backoff y clasificación de errores.
- No versionar los contratos: un cambio incompatible puede enviar eventos válidos hacia consumidores que todavía no conocen el nuevo formato.
- Ignorar el orden: un evento de actualización puede llegar antes que el evento de creación, dependiendo del diseño y de la infraestructura.
- Usar una dead-letter queue sin operación asociada: aislar mensajes no sirve si nadie analiza ni recupera los casos.
- Confundir asincronía con consistencia automática: separar servicios también separa transacciones y responsabilidades.
- Ocultar estados intermedios: el producto y el soporte necesitan saber si una operación está pendiente, rechazada o completada.
También es importante decidir qué procesos requieren eventos y cuáles deberían permanecer síncronos. Si el cliente no puede continuar sin una respuesta inmediata, introducir asincronía puede aumentar la complejidad sin resolver una necesidad real.
Conclusión: diseñar también el fallo del consumidor
La arquitectura orientada a eventos permite desacoplar componentes y procesar operaciones de forma independiente, pero ese desacoplamiento introduce estados intermedios, entregas duplicadas y fallos parciales. El productor puede terminar su trabajo mientras inventario, notificaciones u otros consumidores todavía están pendientes.
Un diseño sólido define las garantías de entrega, implementa reintentos con límites, hace idempotentes los efectos importantes, conserva los mensajes que no pueden procesarse y ofrece trazabilidad desde la solicitud inicial hasta cada consumidor. También convierte la consistencia eventual en estados de negocio explícitos y en políticas de recuperación concretas.
Cuando diseñes un flujo por eventos, define también qué ocurre si el consumidor falla después de que el productor ya dio su trabajo por terminado. Ese escenario no es un detalle operativo: es parte del contrato de la arquitectura.
