La gestión de la consistencia en sistemas distribuidos es un problema fundamental de la computación, especialmente cuando las operaciones involucran múltiples servicios transaccionales que no pueden participar en un protocolo de commit atómico global (como Two-Phase Commit). El patrón Saga emerge como una solución práctica para coordinar transacciones de larga duración, donde cada paso es una transacción local que puede ser compensada por una operación inversa si la transacción global falla. Cloudflare Workflows aborda este desafío al integrar la lógica de compensación directamente en la definición de cada paso, simplificando la implementación de transacciones Saga duraderas y resilientes. Esto es crucial en arquitecturas de microservicios y funciones serverless, donde la atomicidad global es difícil o imposible de lograr, y la consistencia eventual con capacidad de rollback es un requisito operativo.

El problema se agrava con la naturaleza asíncrona y propensa a fallos de los sistemas distribuidos. Un fallo en un paso intermedio de una secuencia de operaciones puede dejar el sistema en un estado inconsistente, con efectos secundarios ya aplicados que necesitan ser revertidos. La implementación manual de esta lógica de compensación es compleja, propensa a errores y difícil de mantener, ya que requiere un seguimiento explícito del estado de cada paso y la orquestación de las operaciones de reversión en el orden correcto. La propuesta de Cloudflare Workflows busca encapsular esta complejidad, permitiendo a los desarrolladores centrarse en la lógica de negocio mientras el framework gestiona la durabilidad y la compensación.

Arquitectura del Sistema

Cloudflare Workflows extiende su API step.do() para incluir un objeto de opciones que puede contener una función rollback asíncrona. Esta función define la lógica de compensación para el paso específico. Cuando un Workflow se ejecuta, el motor de Workflows registra un historial duradero de cada step.do(): su inicio, finalización, salida persistida y si registró un handler de rollback. Este registro de historia es fundamental para la resiliencia y la recuperación.

En caso de fallo terminal del Workflow, el motor utiliza este historial duradero para identificar qué pasos se iniciaron o completaron y, por lo tanto, son elegibles para compensación. Los handlers de rollback se ejecutan en orden inverso al de inicio de los pasos. La ejecución de estos handlers también es duradera, con sus propias configuraciones de reintentos y timeouts, lo que asegura que la compensación misma sea robusta. Para invocar los handlers de rollback, Workflows mantiene 'stubs' (referencias invocables) a las funciones de compensación. Estos stubs son referencias en memoria que permiten al motor llamar a código que reside en otro lugar (por ejemplo, en otro Worker o en una instancia recuperada).

En escenarios de recuperación tras un reinicio del motor (por ejemplo, por un crash o desalojo), los stubs en memoria pueden perderse. En este caso, Workflows entra en un modo de 'replay' donde re-ejecuta el código del Workflow sin volver a ejecutar los cuerpos de los pasos completados. Al encontrar un step.do() completado que tenía un handler de rollback, Workflows reconstruye el stub necesario para la compensación. Esto permite que la lógica de rollback sea recuperable y se ejecute correctamente incluso después de fallos del motor, manteniendo la idempotencia de las operaciones de compensación mediante el uso de idempotencyKeys.

Flujo de Ejecución de Workflow con Rollback

  1. 1 Inicio Workflow El Workflow comienza su ejecución.
  2. 2 step.do() Se ejecuta un paso, registrando su handler de rollback y persistiendo su estado.
  3. 3 step.do() Se ejecuta el siguiente paso, registrando su handler de rollback y persistien...
  4. 4 Fallo de Paso Un paso intermedio falla y el error no es manejado localmente.
  5. 5 Inicio Rollback El Workflow entra en fase de rollback debido al fallo terminal.
  6. 6 Ejecutar Rollback (Paso N) Se invoca el handler de rollback del último paso elegible en orden inverso de...
  7. 7 Ejecutar Rollback (Paso 1) Se invoca el handler de rollback del primer paso elegible en orden inverso de...
  8. 8 Fin Workflow (Errored) El Workflow finaliza en estado de error después de intentar todas las compens...
CapaTecnologíaJustificación
orchestration Cloudflare Workflows Plataforma de orquestación de aplicaciones duraderas y multi-paso, que ahora integra la gestión de rollbacks para transacciones Saga. vs Temporal, Cadence, AWS Step Functions, Azure Durable Functions
storage Durable Step History Mecanismo de persistencia interna de Workflows para registrar el estado de cada paso (iniciado, completado, salida, registro de rollback). Es crucial para la recuperación y la determinación de la elegibilidad de rollback.
networking Workers RPC Protocolo de comunicación interna que permite la invocación de funciones (stubs) a través de diferentes contextos de ejecución, fundamental para invocar handlers de rollback que pueden residir en otras instancias o ser reconstruidos. vs gRPC, Thrift

Trade-offs

Ganancias
  • Consistencia de transacciones distribuidas
  • Resiliencia de aplicaciones multi-paso
  • Simplicidad en la implementación de lógica de compensación
  • Durabilidad de los rollbacks (reintentos, timeouts)
Costes
  • Complejidad de la API (añadir opciones a step.do())
  • Overhead de almacenamiento para el historial de pasos y stubs
const debit = await step.do(
  "debit-account-a",
  async () => {
    return await bankA.debit({
      accountId: fromAccountId,
      amount,
      idempotencyKey: `${transferId}:debit-account-a`,
    });
  },
  {
    rollback: async () => {
      await bankA.credit({
        accountId: fromAccountId,
        amount,
        idempotencyKey: `${transferId}:rollback-debit-account-a`,
      });
    },
  }
);
Muestra cómo adjuntar una función de rollback a un paso de Workflow usando el objeto de opciones, incluyendo el uso de idempotency keys.

Fundamentos Teóricos

El concepto de transacciones distribuidas y la necesidad de consistencia en entornos distribuidos ha sido un tema central en la investigación de sistemas distribuidos desde sus inicios. El patrón Saga, introducido por Hector Garcia-Molina y Kenneth Salem en su paper de 1987 "Sagas", aborda específicamente el problema de transacciones de larga duración que no pueden ser envueltas en un único protocolo de commit atómico. En lugar de un commit atómico global, una Saga es una secuencia de transacciones locales, donde cada transacción local actualiza la base de datos y publica un evento. Si una transacción local falla, se ejecutan transacciones de compensación para deshacer los efectos de las transacciones locales previamente completadas.

Este enfoque contrasta con los protocolos de commit atómico como Two-Phase Commit (2PC), que garantizan la atomicidad pero pueden sufrir de problemas de disponibilidad y rendimiento en sistemas distribuidos a gran escala debido a su naturaleza bloqueante y la necesidad de un coordinador central. El patrón Saga, al aceptar una consistencia eventual y depender de la compensación, ofrece una mayor disponibilidad y escalabilidad, alineándose con los principios de sistemas distribuidos modernos que priorizan la disponibilidad y la tolerancia a particiones (modelo BASE vs. ACID). La implementación de Cloudflare Workflows es una aplicación directa y práctica de los principios del patrón Saga, encapsulando la complejidad de la orquestación y la durabilidad de las compensaciones.