Un ingeniero revisa en una sala de operaciones varias pantallas con estados de pedidos y eventos de un sistema distribuido mientras una base de datos central se muestra en un monitor al fondo.

Postgres como base para workflows distribuidos

Postgres transactions como base de consistencia para workflows distribuidos reduce estados intermedios frágiles y simplifica arquitecturas modernas. Aquí ves cómo aplicarlo con ejemplos concretos para equipos en LatAm que quieren menos complejidad y más confiabilidad.

Si construyes sistemas distribuidos, ya conoces el problema: el estado se te escapa entre servicios, colas, retries, timeouts y jobs que quedan a medio camino. Un flujo que parecía simple termina repartido entre Redis, una cola, una tabla de auditoría, un worker y un servicio que “promete” terminar más tarde. Cuando algo falla, no siempre sabes si el pedido quedó cobrado, si el correo salió, si el inventario se reservó o si el paso siguiente siquiera se intentó.

La idea central de este artículo es simple: puedes usar las transacciones de Postgres como base de consistencia para workflows distribuidos. No para reemplazar todo, sino para reducir el número de estados intermedios frágiles y concentrar la verdad del sistema en el mismo lugar donde ya guardas tus datos. Eso baja complejidad, mejora la observabilidad y te da una forma más predecible de coordinar pasos que, de otro modo, vivirían dispersos.

El problema real: demasiados estados, demasiados lugares

Cuando un workflow crece, suele dividirse en piezas pequeñas por razones válidas: desacoplamiento, escalabilidad, resiliencia. El problema aparece cuando cada pieza empieza a guardar una parte distinta de la verdad. Tienes el estado del pedido en una tabla, la cola de tareas en otro lado, el progreso del job en memoria o en Redis, y los eventos de integración en un bus que quizá no es duradero para tu caso de uso.

Ese reparto parece elegante al inicio, pero introduce un costo muy concreto: cada transición necesita coordinación. Si cobras antes de reservar inventario, puedes dejar un pedido pagado sin stock. Si reservas antes de confirmar el pago, puedes bloquear inventario para órdenes que nunca se completan. Si mandas un email antes de persistir el cambio, puedes notificar algo que luego se revierte.

El patrón que suele aparecer es el de estados intermedios frágiles. Por ejemplo: pending_payment, payment_processing, inventory_reserved, email_queued, shipment_requested. Cuantos más estados agregas para “controlar” el flujo, más oportunidades tienes de quedar atrapado entre dos pasos. Y cuando eso pasa, el equipo termina escribiendo scripts manuales, reintentos ad hoc o paneles internos para reparar inconsistencias.

Qué cambia cuando la base es la transacción

Una transacción de Postgres te da una propiedad muy útil: o se guardan todos los cambios, o no se guarda ninguno. En un workflow, eso significa que puedes registrar el avance del proceso y los efectos de negocio de forma atómica, evitando que un paso quede visible sin el otro. No resuelve todo lo distribuido, pero sí elimina una gran clase de problemas locales que luego se vuelven distribuidos por accidente.

La idea no es “meter todo en una sola tabla”. La idea es usar Postgres como ancla de consistencia para el estado del workflow. El resto del sistema puede seguir siendo distribuido, pero el punto donde decides “esto ocurrió” vive en una transacción que también deja trazas duraderas y consultables.

Eso encaja especialmente bien cuando tu aplicación ya depende de Postgres para el core del negocio. Si ya tienes ahí pedidos, usuarios, facturas o inventario, agregar el estado del workflow en la misma base suele ser más barato que introducir otra pieza de infraestructura solo para coordinar pasos.

Cómo se ve un workflow co-localizado con tus datos

La forma más práctica de pensar esto es: el workflow no vive “al lado” de tus datos, vive con tus datos. En lugar de tener un orquestador separado que almacena su propio estado en otro sistema, escribes el progreso del workflow en la misma base donde guardas la entidad principal. Así, el estado del proceso y el estado del negocio evolucionan juntos.

Un ejemplo típico es un checkout. Imagina que el usuario confirma una compra. En una sola transacción puedes:

  1. crear el pedido con estado confirmed,
  2. reservar stock en la tabla de inventario,
  3. registrar un evento interno para procesar el pago,
  4. guardar el paso actual del workflow,
  5. dejar listo un registro para el worker que enviará el correo o disparará la logística.

Si algo falla en el paso 3 o 4, haces rollback y no queda un pedido “a medias”. Si la transacción committea, sabes que el sistema tiene una base consistente desde la cual seguir.

Un ejemplo mínimo en SQL

No necesitas un framework exótico para entender la idea. Un flujo simple podría empezar así:

BEGIN;

UPDATE inventory
SET available = available - 1
WHERE sku = 'SKU-123' AND available > 0;

INSERT INTO orders (id, user_id, status, created_at)
VALUES ('ord_1001', 'usr_42', 'confirmed', NOW());

INSERT INTO workflow_state (workflow_id, order_id, step, updated_at)
VALUES ('wf_7788', 'ord_1001', 'order_created', NOW());

COMMIT;

Si el UPDATE no afecta filas porque no hay stock, puedes abortar antes de crear el pedido. Si el INSERT del workflow falla, no dejas un pedido confirmado sin trazabilidad. La clave es que el sistema no depende de que otro servicio “alcance” a persistir algo después.

Esto no significa que todo el trabajo pesado ocurra dentro de la transacción. Significa que el punto crítico de verdad del negocio sí queda protegido. Lo demás puede seguir en background, con workers y reintentos, pero arrancando desde un estado ya consistente.

Patrones útiles para no romper la consistencia

Hay varias formas de aprovechar Postgres sin convertirlo en un cuello de botella. La más conocida es el outbox pattern, donde escribes en la misma transacción el cambio de negocio y un registro de evento para procesar después. Otra opción es usar una tabla de jobs o de workflow state que los workers consumen con locking controlado.

La ventaja es que reduces la dependencia de mensajes “fantasma” que podrían perderse entre el commit y el publish. Según la documentación oficial de Postgres, SKIP LOCKED permite que múltiples workers tomen trabajo sin bloquearse entre sí, algo muy útil para colas basadas en tabla. Puedes revisar la referencia oficial aquí: https://www.postgresql.org/docs/current/sql-select.html

Outbox, jobs y estado del workflow

Estos tres enfoques no compiten tanto como parecen. En muchos sistemas terminan combinados:

  • Outbox: guardas un evento confiable en la misma transacción del negocio.
  • Jobs table: registras tareas pendientes para que workers las procesen.
  • Workflow state: guardas el paso actual y sus metadatos para reanudar o auditar.

La diferencia principal está en el objetivo. El outbox sirve para publicar hacia afuera sin perder eventos. La tabla de jobs sirve para ejecutar trabajo asíncrono. El workflow state sirve para saber en qué punto está el proceso y decidir el siguiente paso.

En una arquitectura sencilla, puedes usar solo una tabla de workflow y una de outbox. En una más madura, separas responsabilidades, pero mantienes la misma regla: todo lo que define la verdad del negocio se escribe dentro de una transacción.

Cuándo usar locking explícito

No todos los workflows necesitan locks pesados, pero algunos sí. Si dos workers pueden intentar procesar el mismo pedido, necesitas exclusión mutua. Ahí entran SELECT ... FOR UPDATE, NOWAIT o SKIP LOCKED, según el caso.

Ejemplo práctico: si tu worker busca órdenes pendientes, puede tomar una fila, marcarla como processing y seguir. Si otro worker intenta tomarla al mismo tiempo, SKIP LOCKED evita que ambos hagan el mismo trabajo. Eso reduce duplicados sin obligarte a inventar una coordinación externa más compleja.

Un patrón frecuente es este:

BEGIN;

WITH next_job AS (
  SELECT id
  FROM workflow_jobs
  WHERE status = 'pending'
  ORDER BY created_at
  FOR UPDATE SKIP LOCKED
  LIMIT 1
)
UPDATE workflow_jobs
SET status = 'processing', started_at = NOW()
WHERE id IN (SELECT id FROM next_job)
RETURNING *;

COMMIT;

Con eso, el worker obtiene una tarea de forma segura. Si no hay filas disponibles, simplemente no toma nada y puede reintentar más tarde.

Qué ganas en arquitectura y operación

La primera ganancia es menos piezas. Si el estado vive en Postgres, ya no necesitas otro almacén para la coordinación básica del workflow. Eso reduce configuración, despliegues, backups, permisos, monitoreo y puntos de falla. Para equipos chicos o medianos, ese ahorro se nota rápido.

La segunda ganancia es observabilidad. Puedes consultar el estado del workflow con SQL, unirlo con pedidos, usuarios o facturas, y responder preguntas operativas sin cruzar tres sistemas. Por ejemplo: cuántos pedidos están en processing, cuántos llevan más de 10 minutos sin avanzar, qué cliente tiene más reintentos, o en qué paso falla más seguido el flujo.

La tercera ganancia es que los retries dejan de ser una ruleta. Si una tarea se cae después del commit, el sistema sabe qué quedó persistido y qué falta. No tienes que inferir el estado desde logs o desde una cola que quizá ya no conserva suficiente contexto.

Costos y límites que sí debes tener en cuenta

Esto no es magia y tampoco conviene usarlo para todo. Si tu workflow dispara miles de eventos por segundo o requiere coordinación entre varias bases de datos, Postgres puede seguir siendo parte de la solución, pero no necesariamente el único componente. También debes cuidar la duración de las transacciones: mantener locks por demasiado tiempo afecta concurrencia.

Hay otra regla práctica: no metas trabajo externo dentro de la transacción. Llamar a una API, enviar un email o esperar una respuesta de terceros mientras mantienes la transacción abierta es mala idea. Lo correcto es persistir el estado, commit, y luego ejecutar el side effect desde un worker o proceso separado.

Si necesitas una referencia para entender mejor el comportamiento de transacciones y aislamiento, la documentación oficial de Postgres es un buen punto de partida: https://www.postgresql.org/docs/current/tutorial-transactions.html

Un diseño práctico para equipos en producción

Si vas a aplicar este enfoque, conviene empezar con una estructura simple. No necesitas rediseñar toda tu plataforma. Basta con identificar un flujo crítico, normalmente uno que hoy depende de varios servicios y donde un error deja trabajo manual.

Una arquitectura razonable podría verse así:

  • una tabla principal del negocio, por ejemplo orders,
  • una tabla workflow_state con el paso actual y timestamps,
  • una tabla workflow_outbox para eventos confiables,
  • uno o más workers que leen esas tablas con locking controlado,
  • métricas que midan tiempo por paso, reintentos y fallos.

El truco está en que el worker no “adivina” el estado. Lee una verdad persistida, decide el siguiente paso y actualiza todo dentro de una nueva transacción. Eso vuelve el flujo más fácil de depurar porque cada transición queda registrada con contexto.

Ejemplo de flujo de compra

Supón que vendes un curso o un producto físico. El flujo podría ser:

  1. El usuario confirma el checkout.
  2. Tu backend abre una transacción.
  3. Inserta el pedido y reserva inventario.
  4. Guarda el paso awaiting_payment_capture.
  5. Escribe un evento en outbox.
  6. Commit.
  7. Un worker toma el evento y llama al proveedor de pagos.
  8. Otro paso actualiza el pedido a paid o payment_failed.

Con este diseño, el checkout no depende de que el proveedor de pagos responda en tiempo real para persistir el pedido. Tu sistema siempre sabe qué pasó antes y qué falta después.

Tabla resumen

Pregunta cortaRespuesta corta
¿Qué problema resuelve?Evita estados intermedios frágiles en workflows distribuidos.
¿Dónde vive la verdad del flujo?En Postgres, junto con los datos del negocio.
¿Qué patrón ayuda más?Outbox, jobs table y workflow state dentro de transacciones.
¿Cómo evitas duplicados?Con locking como FOR UPDATE SKIP LOCKED.
¿Qué no debes hacer?Llamar APIs externas dentro de la transacción.
¿Cuándo no alcanza?Cuando necesitas coordinación entre múltiples bases o altísimo volumen.

Tabla resumen

Pregunta cortaRespuesta corta
¿Postgres reemplaza una cola?No siempre, pero sí puede coordinar trabajo de forma confiable.
¿Sirve para equipos pequeños?Sí, porque reduce infraestructura y simplifica operación.
¿Qué gana tu equipo?Menos bugs de consistencia y más trazabilidad.
¿Qué gana producto?Menos pedidos atascados y menos reparación manual.
¿Qué gana soporte?Consultas SQL para ver el estado real del flujo.

Si te quedas con una sola idea, que sea esta: no necesitas separar el estado del workflow de tus datos principales por defecto. Muchas veces, mover esa coordinación a Postgres te da más confiabilidad con menos piezas. Y en sistemas reales, menos piezas suele significar menos cosas que se rompen al mismo tiempo.

Preguntas frecuentes

¿Postgres puede coordinar workflows distribuidos de verdad?
Sí, para una clase muy útil de workflows. No reemplaza todos los brokers ni todos los orquestadores, pero sí puede ser la base de consistencia para pasos de negocio, colas internas y estados de proceso. Eso te permite reducir estados intermedios frágiles y simplificar la arquitectura.
¿Esto significa que ya no necesito Kafka, SQS o RabbitMQ?
No necesariamente. Si tu sistema depende de alto volumen, integración con muchos consumidores o eventos de dominio muy desacoplados, esos componentes siguen teniendo sentido. La idea es que el estado crítico del workflow no dependa solo de ellos para ser consistente.
¿Qué patrón conviene más para empezar?
Para la mayoría de equipos, el outbox pattern es un buen punto de partida. Te permite escribir el cambio de negocio y el evento a publicar en la misma transacción, evitando la clásica ventana donde el commit ocurre pero el mensaje no sale. Si además necesitas ejecución asíncrona, puedes sumar una tabla de jobs.
¿Cómo evito que dos workers procesen la misma fila?
Usa locking a nivel de fila con `FOR UPDATE SKIP LOCKED` o una estrategia equivalente. Así un worker toma una tarea y los demás saltan esa fila sin bloquearse. Esto es especialmente útil si procesas órdenes, facturas o tareas de background desde una tabla.
¿Hay riesgo de volver Postgres un cuello de botella?
Sí, si abusas de transacciones largas o metes trabajo externo dentro de ellas. La clave es mantener la transacción corta, guardar solo el estado necesario y mover llamadas lentas a procesos posteriores. Bien aplicado, Postgres suele aguantar muy bien este tipo de coordinación.
¿Sirve para una startup o solo para sistemas grandes?
Sirve mucho para startups, justamente porque reduce complejidad operativa. Cuando todavía no quieres sumar otra pieza de infraestructura, usar Postgres como base de consistencia te da orden, trazabilidad y menos trabajo manual. Luego puedes escalar hacia otras herramientas si el caso lo pide.
¿Qué tipo de workflows se benefician más?
Los que tienen pasos secuenciales y efectos de negocio claros: checkout, reservas, aprobaciones, onboarding, facturación y sincronización interna. Si el flujo necesita saber exactamente qué ocurrió antes de seguir, Postgres suele encajar muy bien. Si el proceso es puramente event-driven y masivo, quizá necesites combinarlo con otras piezas.

Azirgo

¿Listo para construir tu Producto Digital?

Sitios web, apps móviles, software a medida y soluciones blockchain. Cuéntanos qué tienes en mente y armamos un plan claro contigo.

  • Cotización clara en 48 horas
  • Equipo en Ecuador, atención en español
  • Desde un MVP hasta un producto en producción