La gestión de estado y la coordinación de operaciones en sistemas distribuidos presentan desafíos fundamentales relacionados con la idempotencia y la atomicity. Tradicionalmente, estos problemas se abordan mediante la separación de preocupaciones, donde el estado del workflow reside en un sistema y los datos de la aplicación en otro, requiriendo mecanismos de coordinación complejos como tablas de bookkeeping o patrones de 'Transactional Outbox'.
Este artículo propone una tesis contraria: la co-ubicación del estado del workflow y los datos de la aplicación dentro de la misma base de datos transaccional, específicamente PostgreSQL, puede simplificar drásticamente la construcción de sistemas distribuidos robustos. Al aprovechar las garantías ACID de una base de datos relacional para ambas preocupaciones, se pueden lograr semánticas de 'exactly-once' y atomicity con menos complejidad operativa y de desarrollo, eliminando clases enteras de fallos parciales y condiciones de carrera.
La relevancia de esta aproximación radica en la capacidad de reducir la superficie de error y la complejidad inherente a la coordinación entre sistemas distribuidos. En lugar de construir capas de consistencia sobre múltiples almacenes de datos, se delega esta responsabilidad a un único sistema transaccional maduro, lo que es particularmente valioso en entornos donde la consistencia estricta es un requisito no negociable.
Arquitectura del Sistema
La arquitectura propuesta se centra en un motor de workflow que opera directamente dentro de la misma instancia de PostgreSQL que los datos de la aplicación. Este motor no es un sistema externo que interactúa con la base de datos, sino que sus operaciones de checkpointing y gestión de estado se realizan como parte integral de las transacciones de la aplicación.
Para la idempotencia, el motor de workflow proporciona una transacción de base de datos a cada paso del workflow. Dentro de esta transacción, el paso realiza sus actualizaciones de datos y, crucialmente, el motor de workflow registra su checkpoint. Si la transacción se completa con éxito, tanto las actualizaciones de datos como el checkpoint se persisten atómicamente. Si ocurre algún fallo antes del commit, toda la transacción se revierte, incluyendo tanto las actualizaciones de datos como el checkpoint, asegurando que el paso se pueda re-ejecutar de forma segura sin efectos secundarios duplicados. Esto elimina la necesidad de tablas de bookkeeping a nivel de aplicación para la idempotencia.
Para la atomicity en la coordinación entre sistemas (ej. actualización de base de datos y envío de notificación), se utiliza un patrón similar al 'Transactional Outbox', pero implementado de forma nativa dentro de PostgreSQL. En lugar de una tabla de outbox separada y un proceso de polling externo, se emplea una función definida por el usuario (UDF) en PostgreSQL, como enqueue_workflow. Esta UDF crea una fila en la base de datos que representa el workflow a ejecutar, en la misma transacción que la actualización de los datos de la aplicación. Esto garantiza que, o bien la actualización de la aplicación y el encolamiento del workflow ocurren atómicamente, o ninguno de los dos. Un worker asíncrono, que también opera sobre la misma base de datos, desencola y ejecuta estos workflows. Este enfoque reduce la complejidad operativa al eliminar la necesidad de infraestructura de polling y reconciliación externa.
Flujo de Ejecución de Paso Transaccional con Checkpoint
- 1 Aplicación Inicia un paso de workflow
- 2 Motor de Workflow Proporciona una transacción DB al paso
- 3 Paso de Workflow Realiza actualizaciones de datos en la DB
- 4 Motor de Workflow Registra checkpoint del paso en la misma transacción
- 5 PostgreSQL Commit atómico de la transacción (datos + checkpoint)
- 6 PostgreSQL Rollback si falla antes del commit
Flujo de Encolamiento Atómico de Workflow (Transactional Outbox Co-ubicado)
- 1 Aplicación Realiza actualización de datos
- 2 Aplicación Llama a UDF 'enqueue_workflow' en la misma transacción
- 3 PostgreSQL UDF crea fila de workflow en la DB
- 4 PostgreSQL Commit atómico de la transacción (datos + encolamiento)
- 5 Worker Asíncrono Desencola y ejecuta el workflow
| Capa | Tecnología | Justificación |
|---|---|---|
| storage | PostgreSQL | Base de datos transaccional principal para datos de aplicación y estado de workflow. Proporciona garantías ACID para la co-ubicación. vs Bases de datos NoSQL (ej. Cassandra, MongoDB), Bases de datos relacionales distribuidas (ej. CockroachDB, YugabyteDB) |
| orchestration | Motor de Workflow (interno) | Gestiona la ejecución, checkpointing y recuperación de workflows, integrando sus operaciones directamente en las transacciones de PostgreSQL. vs Motores de workflow externos (ej. Temporal, Cadence, Apache Airflow), Sistemas de colas de mensajes (ej. Kafka, RabbitMQ) con lógica de coordinación externa |
| data-processing | PostgreSQL UDFs (User-Defined Functions) | Permite la ejecución de lógica de encolamiento de workflows dentro de la misma transacción de base de datos que las actualizaciones de la aplicación, garantizando atomicity. vs Lógica de aplicación externa con patrón Transactional Outbox manual, Triggers de base de datos |
Trade-offs
Ganancias
- ▲ Simplificación de la lógica de idempotencia a nivel de aplicación
- ▲ Garantía de atomicity para operaciones distribuidas
- ▲ Reducción de la complejidad operativa (menos componentes externos)
- ▲ Eliminación de ventanas de inconsistencia por fallos parciales
Costes
- △ Mayor carga transaccional en la base de datos principal
- △ Potencial cuello de botella si PostgreSQL se convierte en el único punto de contención
- △ Acoplamiento más estrecho entre la lógica de workflow y la base de datos
Fundamentos Teóricos
Los desafíos de idempotencia y atomicity en sistemas distribuidos tienen profundas raíces en la ciencia de la computación. El concepto de transacciones atómicas, consistentes, aisladas y duraderas (ACID) fue formalizado en la década de 1970, con trabajos seminales como los de Jim Gray sobre sistemas de bases de datos transaccionales. La garantía de 'exactly-once' es un objetivo fundamental en la computación distribuida, a menudo difícil de lograr sin compromisos, y está directamente relacionada con los principios de tolerancia a fallos y recuperación.
El patrón de 'Transactional Outbox' es una aplicación práctica del concepto de 'Two-Phase Commit' (2PC) simplificado para un escenario donde una de las partes es una base de datos transaccional y la otra es un sistema de mensajería asíncrono. Aunque 2PC es conocido por sus problemas de disponibilidad en entornos distribuidos, el 'Transactional Outbox' mitiga esto al delegar la durabilidad inicial a la base de datos local y luego procesar asíncronamente. La propuesta de co-ubicación lleva esto un paso más allá, internalizando la lógica del outbox y el motor de workflow dentro del mismo dominio transaccional, lo que reduce la complejidad de coordinación que 2PC intenta resolver entre sistemas dispares. La eliminación de la necesidad de lógica de idempotencia a nivel de aplicación se alinea con el principio de 'fail-stop' o 'fail-safe' en el diseño de sistemas, donde los componentes fallan de manera predecible y recuperable, sin dejar estados inconsistentes que requieran compensación manual.