El problema fundamental que aborda este patrón es cómo lograr la coordinación distribuida y la tolerancia a fallos para cargas de trabajo de procesamiento por lotes (batch processing) sin introducir la complejidad operativa de un sistema de mensajería dedicado. Tradicionalmente, frameworks como Spring Batch han dependido de brokers de mensajes (Kafka, RabbitMQ) para distribuir y gestionar particiones de trabajo entre múltiples nodos. Sin embargo, para muchos equipos y escenarios de escala pequeña a mediana, la sobrecarga operativa de mantener un broker adicional es significativa, especialmente cuando ya se requiere una base de datos relacional para el repositorio de metadatos del propio Spring Batch.

Este patrón propone reutilizar la base de datos relacional existente como el único plano de coordinación. La tesis es que, mediante el uso inteligente de primitivas de base de datos como transacciones atómicas, bloqueos a nivel de fila y mecanismos de compare-and-set, es posible replicar las funcionalidades clave de un broker (distribución de trabajo, garantía de ejecución única, detección y recuperación de fallos) con una pila tecnológica más simple. Esto es particularmente relevante en un contexto donde la simplicidad operativa y la minimización de costos de infraestructura son factores críticos, y donde la escala no justifica la complejidad de un sistema de mensajería de alto rendimiento.

Arquitectura del Sistema

La arquitectura propuesta se basa en un conjunto de nodos JVM pares que interactúan exclusivamente con una base de datos relacional compartida. Esta base de datos actúa como el repositorio de metadatos de Spring Batch y como el plano de coordinación. Se introducen tres tablas adicionales: BATCH_NODES para registrar nodos activos, su estado y carga; BATCH_PARTITIONS para gestionar el ciclo de vida de cada partición de trabajo, incluyendo su asignación a un nodo y su estado (PENDING, CLAIMED, COMPLETED, FAILED); y BATCH_JOB_COORDINATION para vincular una ejecución de trabajo a su nodo maestro y estado general.

La coordinación es descentralizada. El nodo que lanza un trabajo se convierte en su 'maestro' para esa ejecución específica, evitando la necesidad de un servicio de coordinación centralizado como Apache ZooKeeper o un mecanismo de elección de líder global. Este maestro consulta los nodos vivos en BATCH_NODES para dimensionar el trabajo, escribe las particiones en BATCH_PARTITIONS y las monitorea. Cada nodo, incluyendo el maestro, ejecuta un bucle continuo que se registra en BATCH_NODES con un heartbeat, publica su carga actual y sondea BATCH_PARTITIONS para reclamar y ejecutar las particiones asignadas. La reclamación de particiones se realiza mediante una operación UPDATE con una cláusula WHERE que garantiza la unicidad de la ejecución (compare-and-set), evitando la doble ejecución. La recuperación de fallos se gestiona mediante un mecanismo de heartbeat de dos fases: un nodo que no envía heartbeats se marca primero como 'unreachable' y luego, tras un período de gracia, se 'elimina', reasignando sus particiones transferibles o marcando las no transferibles como FAILED.

Flujo de Ejecución de Job Distribuido

  1. 1 Lanzamiento de Job Un nodo lanza un job y se convierte en maestro para esa ejecución.
  2. 2 Consulta de Nodos Vivos El maestro consulta BATCH_NODES para dimensionar el split.
  3. 3 Creación de Particiones El maestro escribe filas PENDING en BATCH_PARTITIONS, asignando cada una a un...
  4. 4 Polling de Workers Cada nodo (incluido el maestro) sondea BATCH_PARTITIONS para sus particiones ...
  5. 5 Reclamación de Partición El worker ejecuta un UPDATE compare-and-set para cambiar el estado a CLAIMED.
  6. 6 Ejecución de Partición El worker ejecuta la lógica de negocio de la partición.
  7. 7 Actualización de Estado El worker actualiza el estado de la partición a COMPLETED o FAILED.
  8. 8 Monitoreo del Maestro El maestro monitorea BATCH_PARTITIONS hasta que todas las particiones finalizan.

Flujo de Recuperación de Fallos de Nodo

  1. 1 Heartbeat Regular Cada nodo actualiza su timestamp en BATCH_NODES.
  2. 2 Detección de Inactividad (Fase 1) Un nodo con heartbeat > 10s se marca como 'unreachable'.
  3. 3 Período de Gracia El nodo puede recuperarse y enviar un nuevo heartbeat.
  4. 4 Remoción de Nodo (Fase 2) Si el heartbeat > 30s, el nodo se 'elimina' de BATCH_NODES.
  5. 5 Resolución de Particiones El maestro reasigna particiones 'transferable' o marca como 'FAILED' las 'non...
  6. 6 Reclamación por Otro Nodo Un nodo vivo reclama la partición reasignada y la ejecuta.
CapaTecnologíaJustificación
orchestration Spring Batch Framework principal para el procesamiento por lotes, extendido para coordinación distribuida. vs Apache Flink, Apache Spark, Akka Cluster
storage Relational Database (e.g., PostgreSQL, MySQL) Repositorio de metadatos de Spring Batch y plano de coordinación distribuida (estado de nodos, particiones, jobs). vs Message Broker (Kafka, RabbitMQ), Distributed Consensus Service (ZooKeeper, etcd) Índices optimizados para consultas de polling y escaneo de finalización.

Trade-offs

Ganancias
  • ▲ Simplicidad Operativa
  • ▲ Reducción de Costos de Infraestructura
  • ▲ Tolerancia a Fallos Automática
  • ▲ Escalabilidad Horizontal (pequeña a mediana)
Costes
  • △ Latencia de Coordinación
  • ▲ Escalabilidad Máxima
  • ▲ Rebalanceo de Carga en Tiempo Real
  • ▲ Contención de Base de Datos
UPDATE batch_partitions
SET status = ?, -- CLAIMED, then COMPLETED or FAILED
last_updated = CURRENT_TIMESTAMP
WHERE step_execution_id = ?
AND assigned_node = ?
AND status IN ('PENDING', 'CLAIMED');
Sentencia SQL para reclamar una partición, asegurando que solo un worker la tome y que no se procese dos veces.

Fundamentos Teóricos

Este patrón se conecta con principios fundamentales de sistemas distribuidos, particularmente en el área de la coordinación y la tolerancia a fallos. La dependencia de una base de datos relacional para la coordinación remite a los conceptos de 'shared-nothing architecture' y 'shared-disk architecture', donde la base de datos actúa como un recurso compartido para el estado. La garantía de ejecución única y la prevención de la doble ejecución mediante operaciones atómicas en la base de datos son aplicaciones prácticas de los principios de atomicidad, consistencia, aislamiento y durabilidad (ACID) inherentes a los sistemas de gestión de bases de datos relacionales. La estrategia de 'compare-and-set' para reclamar particiones es un patrón común en la implementación de estructuras de datos concurrentes y sistemas distribuidos para garantizar la exclusión mutua sin bloqueos explícitos.

Aunque no cita un paper específico, la gestión de la liveness de los nodos a través de heartbeats y un proceso de detección de fallos de dos fases es un patrón bien establecido en la literatura de sistemas distribuidos para la detección de fallos en entornos asíncronos, donde la ausencia de un heartbeat se interpreta como una posible falla. La distinción entre trabajo 'transferible' (at-least-once, idempotente) y 'no transferible' (at-most-once) es una aplicación directa de los modelos de garantía de entrega de mensajes, un concepto central en los sistemas de mensajería y procesamiento de transacciones, donde la idempotencia es clave para la resiliencia frente a reintentos y fallos.