Be Software Architecture Wise
Be Wise Apps

Read in English · Lire en français · Auf Deutsch lesen

Sección 6.7 de 6.8 · Avanzado · 30 min de lectura

Transacciones distribuidas, sagas y el patrón de bandeja de salida (outbox)

Videolección

5 min · narrado en inglés, con subtítulos
Solo escuchar (audio)
Leer la transcripción

Cuando una acción de negocio abarca varios servicios, ninguna transacción por sí sola puede cubrirla. En esta lección aprenderás por qué se evita la confirmación en dos fases entre servicios, cómo las sagas mantienen correctos los procesos en su lugar y cómo enviar mensajes de forma fiable.

Reservar unas vacaciones significa reservar un vuelo, un hotel y un automóvil de alquiler, a tres empresas distintas. Nadie puede hacer que las tres cosas ocurran exactamente en el mismo instante. Así que las reservas una a una. Si resulta que el automóvil de alquiler no está disponible, cancelas el hotel y el vuelo, quizá pagando una penalización por cancelación. Eso es una saga: pasos separados, cada uno con una forma prevista de deshacerlo. La habilidad está en elegir el orden de los pasos y en planificar las cancelaciones antes de que algo salga mal.

La forma clásica de hacer cambios de todo o nada en varias bases de datos es la confirmación en dos fases, que ejecuta un coordinador. En la fase uno, pregunta a cada participante si puede confirmar. Cada uno hace duraderos sus cambios, conserva sus bloqueos y vota que sí. Al votar que sí, renuncia al derecho de abortar por su cuenta. Si todos los votos son afirmativos, el coordinador registra su decisión: confirmar. En la fase dos, se lo comunica a cada participante, y estos aplican el resultado y liberan sus bloqueos. Pero si el coordinador se cae después de los votos, los participantes no pueden decidir solos, así que conservan sus bloqueos hasta que se recupere. Entre servicios con propietarios independientes, eso encaja mal. La disponibilidad se multiplica a la baja, los bloqueos se mantienen a través de la red, los despliegues quedan acoplados y muchos participantes, como un proveedor de pagos, no pueden unirse en absoluto.

La alternativa es una saga: una secuencia de transacciones locales, cada una en la base de datos propia de un servicio, enlazadas mediante mensajes. Cada paso se empareja con una transacción compensatoria que lo deshace en un sentido de negocio. O se completan todos los pasos, o los ya completados se compensan en orden inverso. Lo que una saga no te da es aislamiento: entre un paso y otro, los demás pueden ver el estado intermedio.

Esta es la saga del pedido de la plataforma de reparto de comida, que ejecuta el servicio de pedidos como orquestador. Primero, registra el pedido como pendiente, lo que indica a los demás que el pedido está en cambio. Segundo, pide al servicio de pagos que autorice el pago: una retención barata en la tarjeta, fácil de anular. Tercero, pide al servicio de despacho que reserve un repartidor, el paso con más probabilidad de fallar en las horas de mayor actividad. Esta vez no hay ningún repartidor disponible. Así que el orquestador compensa los pasos completados, del más reciente al más antiguo. Anula la autorización del pago. Después marca el pedido como rechazado, conservando la fila para auditoría, y avisa al cliente antes de que se haya cocinado o cobrado nada. Si se hubiera encontrado un repartidor, el paso cuatro, la confirmación al restaurante, sería el pivote. Una vez que una cocina empieza a cocinar, la saga solo puede seguir adelante, así que los pasos posteriores se reintentan hasta que tienen éxito.

Un rollback hace que sea como si nada hubiera ocurrido. Una compensación es una transacción nueva que contrarresta algo que sí ocurrió y que puede haberse visto. Puede anular la retención de la tarjeta, pero no puede deshacer el envío de un correo electrónico. Así que las compensaciones deben ser idempotentes, porque lo que las desencadena puede llegar dos veces, y reintentables hasta que tengan éxito, o lanzar una alerta para una persona. Incluso pueden llegar antes que el paso que deshacen. En ese caso, el servicio de pagos debe recordar la anulación y rechazar la autorización tardía. Y los pasos que no pueden deshacerse, como cocinar la comida, van después del pivote, o necesitan una compensación de negocio, como un vale o un reembolso.

Cada paso de una saga actualiza su base de datos y envía un mensaje. Hacer esas dos cosas por separado es el problema de la escritura dual: si hay una caída entre ambas, el mensaje se pierde o anuncia algo que nunca ocurrió. La bandeja de salida transaccional lo resuelve con una sola transacción local. El servicio de pedidos escribe juntos la fila del pedido y el mensaje saliente, como una fila en una tabla de bandeja de salida. Se confirman ambos, o ninguno. Un retransmisor independiente lee la bandeja de salida, consultándola periódicamente o leyendo el registro de cambios de la base de datos, y publica cada mensaje en el broker. Un retransmisor puede caerse antes de anotar lo que ha enviado, así que un mensaje puede salir dos veces. Por eso el receptor mantiene una bandeja de entrada con los ID de los mensajes procesados, guardada junto con su cambio, y omite los duplicados.

Entonces, ¿cómo eliges? Prefiere una sola transacción local siempre que puedas. Si una división de servicios atraviesa un único límite de consistencia, el arreglo más barato suele ser mover el límite. Usa una saga cuando los pasos puedan compensarse o reintentarse. La coreografía, en la que los servicios simplemente reaccionan a los eventos de los demás, es adecuada para dos o tres pasos. Para flujos más largos, con ramificaciones y timeouts, la mayoría de los profesionales prefiere un orquestador. Y ordena los pasos de forma deliberada: los fallos probables antes del pivote, y todo lo irreversible después.

La sección completa incluye la tabla de pasos entera y las contramedidas para la falta de aislamiento de las sagas. A continuación, en la Sección seis punto ocho, veremos las arquitecturas serverless y basadas en espacios. Nos vemos allí.

Lo que necesitas saber

El problema: una acción de negocio, varias bases de datos

Dentro de una sola base de datos, una transacción hace atómicos varios cambios: la fila del pedido, sus artículos y la referencia del pago se guardan todos o ninguno (Sección 0.5). En cuanto los datos se reparten entre servicios que tienen cada uno su propia base de datos, como recomiendan los microservicios y la base de datos por servicio, una sola acción de negocio, como hacer un pedido de comida, afecta a varias bases de datos que pertenecen a servicios distintos: la del servicio de pedidos, la del servicio de pagos y la del servicio de despacho. Ninguna transacción local puede abarcarlas todas.

Hay dos familias de respuestas. Una es hacer que muchos gestores de recursos actúen como uno solo mediante un protocolo de confirmación atómica distribuida, del que la confirmación en dos fases es el clásico. La otra es aceptar que la acción es una secuencia de transacciones locales y diseñar para los momentos intermedios: la saga. Una tercera respuesta, a menudo la mejor, es darse cuenta de que el límite de la transacción y el límite del servicio no coinciden y mover uno de ellos. Esta sección trata las tres y termina con una guía para elegir.

La confirmación en dos fases y XA

La confirmación en dos fases (2PC) es un protocolo, de finales de la década de 1970, que proporciona atomicidad de todo o nada entre varios participantes, como dos bases de datos o una base de datos y un intermediario de mensajes (message broker). Lo ejecuta un coordinador (el gestor de transacciones). En la fase 1, preparación, el coordinador pregunta a cada participante si puede confirmar. Un participante que responde que sí debe primero hacer duraderos los cambios y mantener los bloqueos correspondientes, porque al votar que sí renuncia al derecho de abortar por su cuenta. Si todos los votos son afirmativos, el coordinador escribe su decisión, COMMIT, en su propio registro; si algún voto es negativo o un participante no responde, la decisión es ABORT. En la fase 2, confirmación, comunica el resultado a cada participante, que lo aplica y libera sus bloqueos.

El punto débil es que el 2PC es un protocolo bloqueante. Un participante que ha votado que sí queda en duda: no conoce el resultado y no puede adivinarlo con seguridad, porque puede que otro participante ya haya confirmado. Si el coordinador cae en ese momento y no se recupera, los participantes en duda conservan sus bloqueos hasta que se recupere, y las demás transacciones que tocan esas filas también esperan. Los operadores pueden forzar una decisión heurística para liberar los bloqueos, a riesgo de romper la atomicidad.

La especificación XA, publicada por X/Open en 1991, estandarizó la interfaz entre un gestor de transacciones y los gestores de recursos para que, por ejemplo, un servidor de aplicaciones Java pueda confirmar de forma atómica una actualización de base de datos y el envío de un mensaje. Muchas bases de datos relacionales y algunos brokers de mensajes la admiten.

La confirmación en dos fases y dónde se bloquea

Confirmación en dos fases: el coordinador pide a cada participante que se prepare, recoge los votos afirmativos, registra su decisión y ordena confirmarCoordinadorBD pedidosBD de pagosFase 1:preparar¿preparar?¿preparar?sí (con bloqueos)sí (con bloqueos)anota: COMMITaquí cae el coordinadorFase 2:commitcommitcommithechohechoSi el coordinador falla tras los votos, quienes votaron que sí no pueden decidir solos y conservan sus bloqueos
Los participantes que han votado que sí mantienen sus bloqueos hasta conocer la decisión; si el coordinador falla en el punto resaltado, quedan atascados.

Por qué se evita el 2PC entre microservicios

El 2PC funciona y todavía se usa dentro de los límites de un único sistema. Entre servicios con propietarios independientes suele encajar mal, por razones que se agravan entre sí.

La disponibilidad se multiplica a la baja. Todos los participantes y el coordinador deben estar activos para que cualquier transacción se confirme, de modo que la disponibilidad de la transacción es el producto de las suyas, la misma aritmética que la de una cadena de llamadas síncronas (consulta por qué distribuir). Los bloqueos se mantienen a través de la red durante al menos dos viajes de ida y vuelta, y mucho más si algo va lento, lo que reduce el rendimiento (throughput) y aumenta la contención. El acoplamiento vuelve: todos los participantes deben hablar el mismo protocolo, compartir un gestor de transacciones y ponerse de acuerdo en los timeouts, lo que socava la desplegabilidad independiente que justificó separar los servicios. Muchos participantes no pueden unirse: la mayoría de las API HTTP, los servicios de terceros como los proveedores de pagos, muchas bases de datos NoSQL y varios brokers populares no admiten XA. Y la API del proveedor de pagos nunca mantendrá un bloqueo para tu coordinador.

El panorama es distinto dentro de las bases de datos distribuidas. Sistemas como Google Spanner ejecutan internamente la confirmación en dos fases entre grupos de réplicas que a su vez se mantienen consistentes mediante consenso, de modo que un coordinador caído se sustituye en lugar de esperarlo. Eso hace viables las transacciones entre particiones dentro de una misma base de datos; no hace viables las transacciones entre servicios con propietarios distintos y con tecnologías diferentes.

Sagas: una secuencia de transacciones locales

Hector Garcia-Molina y Kenneth Salem introdujeron la saga en un artículo de SIGMOD de 1987 sobre las transacciones de larga duración, aquellas que mantendrían bloqueos de base de datos durante horas. Su idea era dividir una transacción así en una secuencia de transacciones ordinarias T1, T2 … Tn, cada una de las cuales se confirma por su cuenta, y emparejar cada una con una transacción compensatoria Ci que la deshace semánticamente. El sistema garantiza entonces que, o bien se completa toda la secuencia, o bien los pasos completados van seguidos de sus compensaciones en orden inverso: T1 … Tj, Cj … C1.

Las arquitecturas de microservicios adoptaron la idea porque encaja exactamente con sus restricciones: cada paso es una transacción ACID local en la base de datos propia de un servicio, y los pasos se enlazan mediante mensajes. Una saga proporciona atomicidad en el sentido eventual, de negocio (todos los pasos ocurren o sus efectos se deshacen), pero no aislamiento: entre un paso y otro, otras transacciones pueden ver el estado intermedio.

Ayuda clasificar los pasos, como hace Chris Richardson en Microservices Patterns (2018). Las transacciones compensables pueden deshacerse mediante una compensación. La transacción pivote es el punto de decisión definitivo: una vez que se confirma, la saga se ejecutará hasta el final. Las transacciones reintentables van después del pivote y deben diseñarse para que acaben teniendo éxito si se reintentan. Situar los pasos con más probabilidad de fallar antes del pivote, y los pasos que no pueden deshacerse después de él, es una decisión de diseño clave.

Coreografía u orquestación

Los pasos de una saga deben coordinarse de algún modo, y las dos opciones reflejan la coreografía y la orquestación tratadas en arquitectura dirigida por eventos.

En una saga basada en coreografía no hay coordinador. Cada servicio escucha eventos y reacciona realizando su transacción local y publicando un evento nuevo: el servicio de pedidos publica OrderCreated, el servicio de pagos reacciona y publica PaymentAuthorised, el servicio de despacho reacciona a ese evento, y así sucesivamente; los eventos de fallo desencadenan compensaciones en los servicios que los escuchan. Es sencilla para dos o tres pasos y mantiene los servicios débilmente acoplados. A medida que crecen los pasos, el flujo solo existe de forma implícita, disperso entre los servicios, por lo que es difícil verlo, probarlo y cambiarlo; y se cuelan dependencias cíclicas de eventos entre servicios.

En una saga basada en orquestación, un único orquestador, normalmente en el servicio que es propietario del proceso de negocio, envía comandos a los participantes y reacciona a sus respuestas. El orquestador es una máquina de estados persistente: registra en su propia base de datos en qué paso está, de modo que puede reanudarse tras una caída. El flujo es explícito y se puede probar en un solo lugar, y los participantes no necesitan conocerse entre sí. El riesgo es que el orquestador acumule lógica de negocio que corresponde a los participantes. Los motores de flujos de trabajo como Temporal, Camunda y AWS Step Functions ofrecen orquestación duradera como plataforma, a costa de una dependencia más. Para más de unos tres pasos, o para cualquier flujo con ramificaciones y timeouts, la mayoría de los profesionales prefiere la orquestación.

Ejemplo práctico: la saga del pedido

La saga del pedido de la plataforma de reparto de comida la orquesta el servicio de pedidos. El orden de los pasos es deliberado: la autorización del pago es una retención barata que es fácil de anular, la reserva del repartidor es el paso con más probabilidad de fallar en las horas de mayor actividad, y la confirmación al restaurante es el pivote, porque una vez que una cocina ha empezado a cocinar, el pedido no puede deshacerse sin más.

La saga del pedido, paso a paso

PasoServicio y transacción localClaseCompensación si falla un paso posterior
1. Crear el pedidoEl servicio de pedidos inserta el pedido con el estado PENDING (un bloqueo semántico).CompensableC1: cambiar el estado a REJECTED y registrar el motivo. La fila se conserva para auditoría, no se borra.
2. Autorizar el pagoEl servicio de pagos pide al proveedor de la tarjeta que aplique una retención por el total, usando el ID del pedido como clave de idempotencia.CompensableC2: anular la autorización, liberando la retención. Puede que el cliente vea brevemente un importe pendiente en la app de su banco.
3. Reservar un repartidorEl servicio de despacho reserva un repartidor cercano para la franja de recogida.CompensableC3: liberar al repartidor para que pueda aceptar otros trabajos.
4. Confirmar el pedidoEl servicio de pedidos cambia el estado a CONFIRMED y envía la comanda al restaurante.PivoteNinguna: a partir de aquí la saga debe seguir adelante. Los problemas posteriores se gestionan con nuevos procesos de negocio (reembolsos, vales).
5. Avisar al cliente; capturar el pago en la recogidaEl servicio de notificaciones envía "Pedido confirmado"; el servicio de pagos captura el importe retenido cuando el repartidor recoge la comida.ReintentableNinguna: estos pasos se reintentan, de forma idempotente, hasta que tienen éxito o se escalan a una persona.

La saga del pedido cuando no hay ningún repartidor disponible

Diagrama de secuencia: una saga del pedido orquestada en la que falla la reserva del repartidor y los pasos anteriores se compensan en orden inversoPedidosorquestador de sagaServicio de pagosDespacho1. pedido PENDING2. AuthorisePaymentAuthorised3. ReserveCourierNoCourierAvailablecompensar pasos completados en orden inversoC2. VoidAuthorisationVoidedC1. pedido REJECTEDcliente avisado: sin repartidor, sin cargo
El paso 3 falla, así que el orquestador ejecuta las compensaciones de los pasos 2 y 1, del más reciente al más antiguo, y se avisa al cliente antes de que se haya cocinado o cobrado nada.

La compensación no es un rollback

Un rollback de base de datos hace que sea como si la transacción nunca hubiera ocurrido: nadie más llegó a ver sus cambios. Una compensación es una transacción nueva, hacia adelante, que contrarresta semánticamente otra anterior que sí ocurrió y que puede haberse visto. En la saga del pedido, puede que el banco del cliente ya muestre una retención pendiente y que un correo electrónico haya dicho "pago autorizado". La compensación no puede deshacer el envío del correo; solo puede anular la retención y enviar otro mensaje. Algunas acciones no pueden compensarse en absoluto, como la comida ya cocinada o una notificación ya leída; esas van después del pivote, o necesitan una compensación de negocio, como un vale o un reembolso.

Por eso las compensaciones necesitan el mismo cuidado que los pasos hacia adelante. Deben ser idempotentes, porque el mensaje que las desencadena puede entregarse dos veces. Deben ser reintentables hasta que tengan éxito, porque una compensación que falla de forma permanente deja el sistema en un estado que nadie diseñó; si de verdad no pueden tener éxito, deben lanzar una alerta para que una persona lo resuelva. Puede que tengan que ser conmutativas con el paso hacia adelante: si un VoidAuthorisation llega antes que el AuthorisePayment que deshace (algo posible tras timeouts y reintentos), el servicio de pagos debe recordar la anulación y rechazar la autorización tardía en lugar de fallar. Y deben ser visibles para el negocio: "rechazado, sin repartidor" es información que el equipo de operaciones quiere ver en un panel.

Vivir sin aislamiento: anomalías y contramedidas

Como cada paso se confirma por su cuenta, las sagas carecen de la I de ACID, y las sagas concurrentes pueden interferir entre sí. Richardson describe tres anomalías resultantes. Una actualización perdida se produce cuando una saga sobrescribe un cambio hecho por otra sin leerlo, como cuando una saga de cancelación marca un pedido como cancelado y la saga del pedido lo marca después como confirmado. Una lectura sucia se produce cuando una saga lee datos que otra saga ha cambiado pero que todavía puede compensar, como una comprobación del límite de crédito que cuenta una retención que está a punto de anularse. Una lectura difusa o no repetible se produce cuando dos pasos de una misma saga leen los mismos datos y obtienen valores distintos porque otra saga los cambió entretanto.

También cataloga contramedidas, técnicas de diseño que reducen las anomalías, basándose en investigaciones anteriores sobre transacciones entre varias bases de datos. Las más utilizadas son estas.

Contramedidas para la falta de aislamiento en las sagas (según Richardson)

ContramedidaIdeaEjemplo de reparto de comida
Bloqueo semánticoUn paso compensable establece un indicador a nivel de aplicación, normalmente un estado *_PENDING, que avisa a las demás transacciones de que el registro está en cambio; estas esperan, fallan o lo tratan de forma especial.Mientras un pedido está en PENDING, una solicitud de cancelación se pone en cola o se rechaza con "el pedido se está procesando", en lugar de aplicarse a medio camino.
Actualizaciones conmutativasDiseñar las actualizaciones para que den el mismo resultado en cualquier orden, lo que elimina las actualizaciones perdidas.El libro de ganancias del repartidor registra los abonos y los cargos como entradas separadas en lugar de sobrescribir un saldo.
Vista pesimistaReordenar los pasos para minimizar el riesgo de negocio de una lectura sucia, colocando las actualizaciones arriesgadas en pasos reintentables después del pivote.Aumentar la "capacidad disponible" de un restaurante solo en un paso reintentable, cuando la cancelación sea segura, nunca en un paso que pueda compensarse.
Releer el valorAntes de actualizar, volver a leer el registro y comprobar que no ha cambiado desde que se leyó (bloqueo optimista); si ha cambiado, abortar y, posiblemente, reiniciar la saga.Antes de confirmar, el orquestador comprueba que la versión del pedido sigue siendo la que creó; si una cancelación la ha cambiado, compensa en su lugar.
Archivo de versionesRegistrar las operaciones aplicadas a un registro para que las operaciones que llegan fuera de orden puedan reordenarse o reconocerse.El servicio de pagos registra que ha llegado una anulación para una autorización que todavía no ha visto, y rechaza la autorización tardía.
Por valorElegir el mecanismo de coordinación para cada solicitud según su riesgo de negocio.Los pedidos normales usan la saga; un pedido de catering corporativo de gran valor podría pasar por un proceso más estricto y más lento.

Publicar de forma fiable: la bandeja de salida transaccional

Cada paso de una saga hace dos cosas: actualiza su propia base de datos y envía un mensaje (un evento o una respuesta). Hacerlas por separado crea el problema de la escritura dual. Si el servicio confirma y luego se cae antes de publicar, el resto de la saga nunca se entera del paso. Si publica primero y luego falla la confirmación, otros servicios actúan sobre algo que nunca ocurrió. Envolver la base de datos y el broker en un 2PC es justo lo que queríamos evitar.

La bandeja de salida transaccional lo resuelve con una sola transacción local. El servicio escribe el cambio de negocio y el mensaje saliente, como una fila en una tabla outbox, en la misma transacción de base de datos; o se confirman ambos o ninguno. Después, un retransmisor (relay) independiente lee la bandeja de salida y publica cada mensaje en el broker. Hay dos diseños de retransmisor habituales. Un publicador por sondeo (polling publisher) consulta cada cierto tiempo la tabla de bandeja de salida en busca de filas no publicadas y las marca como enviadas: es sencillo, pero añade carga y latencia. El seguimiento del registro de transacciones (transaction log tailing) usa la captura de datos de cambios (CDC): una herramienta lee el propio registro de replicación de la base de datos (el write-ahead log de PostgreSQL o el binary log de MySQL) y convierte las nuevas filas de la bandeja de salida en mensajes. Debezium, una plataforma de CDC de código abierto que suele ejecutarse con Kafka Connect, es el ejemplo más conocido e incluye un enrutador de eventos de bandeja de salida que dirige cada fila a un topic según una columna, como el tipo de agregado.

El retransmisor puede caerse después de publicar y antes de registrar que lo hizo, así que a veces publicará un mensaje dos veces. Por tanto, la bandeja de salida proporciona una entrega al menos una vez, y los consumidores deben saber manejar los duplicados. El orden de publicación se conserva por agregado si el retransmisor publica las filas en el orden de confirmación y usa el ID del agregado como clave del mensaje.

BEGIN;

INSERT INTO orders (id, customer_id, state, total_pence)
VALUES ('1042', 'c-77', 'PENDING', 2350);

INSERT INTO outbox (id, aggregate_type, aggregate_id, type, payload)
VALUES ('e-9f1c', 'order', '1042', 'OrderCreated',
        '{"orderId":"1042","customerId":"c-77","totalPence":2350}');

COMMIT;
-- A relay (poller or CDC such as Debezium) later publishes e-9f1c to the broker.
Hacer un pedido con una bandeja de salida transaccional: el pedido y su evento se confirman juntos, en una sola transacción local.

Bandeja de salida al salir, bandeja de entrada al entrar

Bandeja de salida transaccional: el pedido y su evento se escriben en una transacción local, un retransmisor lo publica y el consumidor filtra duplicados con una bandeja de entradaServ. pedidoshacer pedido: una transacción localordersfila 1042outboxOrderPlacedse confirman ambasfilas o ningunaRelaysondeo o CDCleeBrokertopic: orderspublicaServ. despachoinbox: IDs vistosentrega al menos una vez;se omiten duplicados
La bandeja de salida elimina la escritura dual en el lado emisor; la bandeja de entrada, o cualquier comprobación de duplicados, hace seguro el lado receptor con una entrega al menos una vez.

Recibir de forma fiable: receptores idempotentes y la bandeja de entrada

Como la bandeja de salida, los reintentos del broker y los reintentos de la saga producen duplicados, cada participante debe ser un receptor idempotente (idempotent receiver), el nombre que Gregor Hohpe y Bobby Woolf dieron al patrón en Enterprise Integration Patterns (2003). Algunas operaciones son idempotentes por naturaleza ("cambiar el pedido 1042 a CONFIRMED"); otras necesitan una comprobación de duplicados. La implementación habitual es una bandeja de entrada (inbox): una tabla de ID de mensajes procesados, escrita en la misma transacción local que el cambio de negocio, de modo que "procesar el mensaje" y "recordar que se procesó" se confirman juntos. Un mensaje que se vuelve a entregar encuentra su ID ya presente y se confirma su recepción sin hacer nada. El código del lado del consumidor aparece en arquitectura dirigida por eventos.

Juntos, la bandeja de salida, la entrega al menos una vez y los receptores idempotentes dan el resultado práctico al que la gente suele referirse con "exactamente una vez": cada efecto de negocio ocurre una vez, aunque los mensajes puedan viajar más de una vez.

Guía de decisión

Guía de decisión: ¿una transacción, una saga o nuevos límites?

Úsala cuando una acción de negocio necesite cambiar datos en más de un lugar.

  1. ¿Todos los datos que deben cambiar juntos pertenecen a un solo servicio y se almacenan en una sola base de datos?
    SíUsa una sola transacción ACID local. Es la opción más sencilla y más sólida; no la distribuyas.
    NoPasa a la siguiente pregunta.
  2. ¿Las partes que deben cambiar juntas cambian casi siempre juntas, pertenecen a un solo equipo y comparten un contexto delimitado, de modo que la división es accidental?
    SíRediseña el límite: fusiona los servicios o mueve los datos, para que la acción pase a ser una sola transacción local. Las sagas no arreglan una división equivocada.
    NoPasa a la siguiente pregunta.
  3. ¿Todos los participantes deben ver todos los cambios en el mismo instante, sin que nunca sea visible ningún estado intermedio (por ejemplo, un libro contable que siempre debe cuadrar)?
    SíMantén esos registros en una sola base de datos, o usa una base de datos distribuida que ofrezca internamente transacciones entre particiones. Usa el 2PC solo dentro de los límites de un único sistema, con participantes que controles.
    NoPasa a la siguiente pregunta.
  4. ¿Cada paso puede compensarse, o bien situarse después de un pivote y reintentarse hasta que tenga éxito?
    SíUsa una saga: orquestada si tiene más de unos tres pasos o alguna ramificación, coreografiada si es corta y estable. Añade una bandeja de salida, receptores idempotentes y contramedidas como los bloqueos semánticos.
    NoReordena los pasos o cambia el proceso de negocio (por ejemplo, reservar antes de cobrar, o añadir una revisión manual) hasta que sea posible; si no, reconsidera los límites.

Comprueba lo que has entendido

¿Por qué un participante en una confirmación en dos fases no puede simplemente confirmar por su cuenta si el coordinador desaparece después de que haya votado que sí?

Porque no conoce la decisión: puede que otro participante haya votado que no, así que el coordinador puede haber decidido abortar; adivinar podría romper la atomicidad.

En la saga del pedido, ¿por qué la confirmación del pedido al restaurante es el pivote y no el primer paso?

Una vez que la cocina empieza a cocinar, el paso no puede deshacerse, así que debe ir después de los pasos que podrían fallar (la autorización del pago, la reserva del repartidor), que pueden compensarse con poco costo.

¿Qué problema resuelve la bandeja de salida transaccional y qué garantía ofrece?

Elimina la escritura dual entre la base de datos y el broker al confirmar juntos el cambio y el mensaje; después, el retransmisor entrega el mensaje al menos una vez, así que puede haber duplicados.

Indica dos diferencias entre una compensación y un rollback de base de datos.

Es una transacción nueva que se ejecuta después de que la original se haya confirmado, y puede que otros ya hayan visto la original o actuado en consecuencia; además, puede ser solo una forma semántica de deshacer (un reembolso, una anulación) en lugar de restaurar exactamente el estado anterior.

Siguiente · 6.8Serverless y basada en espaciosDos respuestas opuestas a la capacidad: las funciones serverless y los backends gestionados que escalan a cero y se facturan por uso, y la arquitectura basada en espacios, que atiende picos extremos desde memoria replicada con la base de datos fuera de la ruta crítica.Continuar →

Fuentes y lecturas recomendadas

¿Tienes una pregunta sobre esta sección? ArchitectBot, el asistente de estudio con IA del curso, responde primero a partir del curso.

Pregunta: “Nuestro proceso de pago llama primero al servicio de pagos, luego al de inventario y luego al de envíos, y a veces cobramos a los clientes artículos que no podemos enviar. ¿Cómo deberíamos rediseñarlo como una saga?”