Transactional Outbox

Qué es

Transactional Outbox conserva en una única transacción local el cambio de negocio y un mensaje pendiente de publicación. Un relay independiente lee esos registros confirmados y los envía al broker. La atomicidad está entre estado local e intención durable de publicar, no entre base de datos, broker y consumidores.

El patrón trata el dual-write-problem que aparece al confirmar datos y publicar como acciones independientes. Si el proceso cae después del commit local, la intención sigue disponible para recuperarla; si la transacción aborta, tampoco queda un mensaje publicable. Chris Richardson describe esta separación entre sender, outbox y relay. Transactional Outbox.

Qué problema resuelve

Order Service confirma el pedido 42 y necesita comunicar OrderCreated a Inventory Consumer. Publicar antes del commit permite anunciar un pedido que luego aborta; publicar después deja una ventana donde el proceso puede caer sin avisar a nadie.

Outbox cambia esa ventana por una obligación persistente. No hace que inventario se actualice inmediatamente ni que una publicación imposible termine por ocurrir. El progreso depende de conservar los registros, recuperar el relay y disponer de un destino que acepte los mensajes bajo el contrato requerido.

Cómo funciona

BEGIN DB TRANSACTION
  create order 42
  insert outbox event evt-42-created
COMMIT
 
independent relay
  -> publish evt-42-created
  -> broker acknowledgement
  -> record publication progress

El registro lleva una identidad estable, tipo y versión de mensaje, datos necesarios y destino o información para determinarlo. Estos son datos del protocolo de mensajería, no campos del frontmatter del Atlas. No debe depender de volver a leer el estado actual para reconstruir sin contexto un evento pasado.

Estado y registro tienen que compartir la misma frontera transaccional. Escribir la outbox en otra base, usar otra transacción o dejar el mensaje solo en memoria conserva una ventana de pérdida. El relay publica únicamente trabajo confirmado, no cambios que todavía podrían abortar.

Estrategias de relay

Polling publisher

Un publicador consulta periódicamente registros pendientes, selecciona un lote y registra su progreso tras publicarlo. Permite controlar lotes y pausas, a cambio de consultas, índices, latencia de sondeo y coordinación entre workers. Un claim abandonado debe poder recuperarse después de una caída.

La concurrencia necesita evitar que workers se estorben y, cuando importa, preservar el orden por entidad. No hay que asumir que consultar por timestamp ofrece un orden global de commits. El patrón de polling identifica precisamente el orden como una dificultad de implementación. Polling Publisher.

Transaction log / CDC

Otra estrategia observa el log de transacciones o un stream de cambios confirmados para extraer los registros de outbox. El relay conserva checkpoints y traduce esos registros al contrato del broker. Evita parte del sondeo de la tabla, pero necesita integración con el mecanismo del store y recuperación de offsets.

Una interrupción que excede la retención del log puede requerir reconstrucción o un procedimiento de arranque; borrar registros que todavía necesita recuperar sería peligroso. CDC no convierte automáticamente cualquier cambio de fila en un evento de negocio correctamente diseñado. Transaction Log Tailing.

Failure windows y duplicados

VentanaConsecuencia y recuperación
La transacción local abortaNo se publica su registro de outbox.
Commit local, crash antes de publicarEl relay recupera el registro pendiente.
Broker acepta, respuesta perdidaEl relay puede publicar otra vez con la misma identidad.
Publicación confirmada, crash antes del checkpointLa recuperación puede repetir la publicación.
Se marca publicado antes de enviarPuede perderse la publicación; se rompió la obligación durable.

La duplicación no es una anomalía eliminada por tener outbox. AWS también advierte que el publicador puede producir duplicados y que los consumidores necesitan tratarlos. AWS: Transactional Outbox.

La identidad evt-42-created permanece al reintentar; crear otro ID en cada publicación oculta la repetición. Un segundo evento legítimo del pedido necesita su propia identidad. La confirmación del broker acredita una frontera de delivery-guarantees, no el efecto de Inventory Consumer.

La política de retry del relay decide qué fallos permiten otra publicación. backoff y jitter espacian intentos y reducen oleadas sobre el broker. Agotar un lote de intentos no autoriza borrar la obligación durable: el registro necesita reprogramación o resolución explícita, y la espera no debe mantener abierta la transacción local.

Ejemplo y frontera end-to-end

Inventory Consumer recibe dos publicaciones de evt-42-created. Si ambas decrementan stock sin protección, la outbox no evita el doble descuento. idempotent-consumer aplica idempotency a ese efecto; inbox-pattern puede coordinar identidad y cambio de inventario en una transacción local.

Con esa combinación, se puede proteger el commit del pedido con su intención de publicación y, por separado, el efecto local del consumidor para una identidad retenida. El handler y el relay pueden ejecutarse varias veces. Un correo o una API externa siguen requiriendo su propio contrato. No equivale a exactly-once end-to-end.

Retención, cleanup y operación

El cleanup debe distinguir trabajo pendiente de publicado y respetar checkpoints, reconstrucción y necesidades de replay. La aceptación del broker puede permitir retirar registros según la política acordada; no prueba que cada consumidor terminó. Si se necesita conservar esa evidencia, hay que modelarla explícitamente.

Medir edad del pendiente más antiguo, crecimiento de backlog, fallos de publicación y retraso del relay. Un broker caído puede llenar el store aunque los pedidos sigan confirmándose. Capacidad, límites y tratamiento de mensajes no publicables forman parte de operar la obligación.

La eventual consistency empieza tras el commit local: el pedido existe mientras inventario todavía no lo conoce. La API y los estados de negocio deben admitir esa ventana; un relay rápido no la elimina como posibilidad.

Cuándo usarlo y alternativas

Usarlo cuando cambios locales requieren mensajes durables y se acepta su propagación posterior. Es útil en pasos de saga-pattern, pero una saga coordina el proceso de negocio y la outbox solo esta frontera de publicación.

No agregarlo a trabajo efímero cuya pérdida es aceptable ni presentarlo como commit global. two-phase-commit puede ser otra opción entre recursos compatibles cuando se necesita ese contrato conjunto. Un registro durable existente desde el cual derivar publicaciones también puede evitar una segunda escritura independiente. El costo de outbox incluye almacenamiento, relay, limpieza, identidad, orden y recuperación; el mapa reliable-messaging conecta esas responsabilidades.