← Volver al blog

No podés reiniciar el sistema que está corriendo tu trabajo

2026-09-28

No podés reiniciar el sistema que está corriendo tu trabajo

Hay una paradoja que aparece siempre que construís la herramienta con la que trabajás: para mejorarla tenés que reiniciarla, y reiniciarla cancela el trabajo que está corriendo.

En mi caso el costo de esa paradoja era brutal, porque la cola de trabajo vivía en la memoria del proceso. Cada reinicio del servidor —un deploy, una actualización, un corte de luz— borraba el plan entero. No solo lo que faltaba: también el rastro de qué estaba corriendo y en qué estado había quedado. Y el servidor es, justamente, lo que estoy modificando todo el tiempo.

La solución no es "guardá la cola en una base de datos" y listo. Lo que hay que decidir de verdad es qué significa el estado de una tarea que se cortó por la mitad, y quién tiene derecho a cambiarlo cuando el proceso vuelve a levantar.

Cómo era antes

La cola original era una lista en memoria. Tildabas las tareas que querías correr en la pantalla, el orden era el orden en que las habías seleccionado, y el sistema las procesaba una por una.

Funcionaba mientras nada se interrumpiera. Con dos problemas serios.

El primero es el de arriba: cada vez que quería desplegar una mejora del orquestador, tenía que elegir entre esperar a que la cola terminara o perder la planificación.

El segundo, más silencioso: al primer fallo abortaba la cola completa. Un error en la tercera de diez tareas cancelaba las siete restantes. Y esas siete no tenían nada que ver con la que falló: simplemente estaban detrás en la fila.

El rediseño

La cola se movió a la base de datos, con una decisión de diseño que me gusta especialmente: la posición es un número real, no entero.

Eso permite insertar entre dos elementos sin renumerar toda la lista. Si querés meter algo entre la posición 3 y la 4, le asignás 3,5. Es una técnica vieja y resuelve de raíz el problema de reordenar una lista persistida sin escribir cincuenta filas cada vez que alguien arrastra una fila.

El orden se cura a mano, mezclando arreglos, funcionalidades y auditorías en la secuencia que tenga sentido ese día. Se reordena arrastrando, mientras las tareas estén en espera. La que está corriendo queda fija y bloqueada, que es lo mínimo razonable: no podés reordenar algo que ya empezó.

Qué hacer cuando una tarea falla

Acá había una decisión de producto disfrazada de detalle técnico, y la resolví no resolviéndola: lo convertí en una opción por corrida.

Podés elegir que la cola siga con el resto marcando la fallida, o que frene y deje todo lo demás en espera. Las dos posturas son defendibles según el caso. Si estás procesando diez arreglos independientes, querés que siga. Si la tercera falló porque el entorno se rompió, querés que frene antes de que fallen las siete restantes por el mismo motivo.

Después le agregué una regla más estricta, que en la práctica resultó la más útil: la cola no reanuda más allá de un fallo sin resolver. Podés seguir, pero alguien tiene que haber mirado ese fallo y haber decidido qué hacer con él. Sin eso, la cola se planta.

Es una decisión deliberadamente incómoda. La alternativa —seguir siempre— hace que los fallos se acumulen sin que nadie los vea, que es exactamente el problema de los 1.771 incidentes sin procesar que conté hace unas entregas.

Un solo mecanismo de ejecución

Un efecto secundario del rediseño que no había anticipado: los botones de "correr todo" y "reparar todo" dejaron de ser mecanismos aparte.

Antes cada uno era su propio pipeline, con su propia lógica de avance y sus propios bugs. Ahora son simplemente atajos que encolan en masa con un orden preestablecido y arrancan la cola. Un solo mecanismo de ejecución, todo visible y reordenable.

Es la clase de simplificación que aparece sola cuando la abstracción correcta finalmente existe. Mientras la cola era efímera, tener pipelines paralelos parecía razonable. Con una cola persistente y curable, no tiene ningún sentido.

Sobrevivir al reinicio de verdad

Persistir la cola resuelve la mitad del problema. La otra mitad es qué pasa con lo que estaba corriendo en el momento del reinicio.

Esa tarea queda en un limbo: la base de datos dice que está en curso, pero el proceso que la ejecutaba ya no existe. Si nadie hace nada, se queda ahí para siempre, ocupando el lugar de la cabeza de la cola y bloqueando todo lo demás.

En agosto agregué las dos piezas que faltaban: apagado ordenado del servidor, para que las corridas en curso se marquen antes de morir, y reconciliación al arrancar, que revisa qué quedó declarado como corriendo sin proceso detrás y lo resuelve.

Y una regla que parece obvia y me costó descubrir: antes de lanzar una fila, releer el estado de la tarea. Porque entre que se encoló y le llegó el turno pudo haber pasado cualquier cosa — la tarea pudo haberse completado por otro camino, o haberse pospuesto. Lanzar a ciegas contra una foto vieja es una fuente constante de trabajo duplicado.

Por qué esto resume el proyecto entero

De todo lo que construí en estos meses, la cola persistente es lo que mejor resume la tesis: en un sistema con agentes, el estado del trabajo en curso es tan importante como el trabajo mismo.

Una cola en memoria funciona perfecto hasta el primer reinicio, y el primer reinicio siempre llega en el peor momento. No es una optimización: es la diferencia entre una herramienta que usás cuando estás mirando y un sistema que corre cuando no estás.

Hoy la cola es la única vía de ejecución del sistema. No hay forma de correr una tarea por fuera de ella, y eso —que al principio me pareció una restricción molesta— resultó ser lo que hace que todo lo demás sea observable.

Con la cola sobreviviendo a los reinicios, el sistema podía correr solo durante horas sin que yo estuviera cerca. Y ahí apareció una pregunta que nunca me había hecho, porque nunca lo había dejado correr tanto: cuánto hay que dejarlo pensar antes de interrumpirlo. De eso trata la próxima entrega.