Blog
ingeniería 7 min de lectura

No hay un proceso siempre encendido

Construimos el pipeline como un servicio, lo usamos unas semanas y lo borramos. El reemplazo no mantiene ningún proceso corriendo y soporta mejor los fallos.

Por Erick Agrazal, Fundador

Una computadora pequeña apagada descansa junto a un temporizador y una laptop cerrada

La primera versión de nuestro pipeline era un servicio. Permanecía encendido, tenía una cola, guardaba una máquina de estados en memoria y hasta llevaba un comentario que prometía una base de datos real cuando el asunto se pusiera serio. Lo usamos unas semanas y lo borramos.

Su reemplazo no mantiene ningún proceso corriendo. Tres veces al día, una sesión programada despierta, lee el estado del mundo, hace una parte limitada del trabajo y termina. Entre esas sesiones no hay nada nuestro ejecutándose. No existe una cola esperando ni un proceso enviando señales de vida.

Parece un paso atrás. En realidad, fue la decisión que permitió sobrevivir a todos los fallos que vinieron después. Por eso merece su propia nota.

Lo que costaba el daemon

El servicio funcionaba. El problema era que se convirtió en la cosa que vigilábamos.

Su estado vivía en su memoria y en sus tablas. Eso permitía que no coincidiera con la realidad. Una tarea podía aparecer “en progreso” para el servicio y seguir intacta en git. Un reinicio perdía lo que estaba a medias. Investigar una ejecución mala exigía reconstruir lo que el servicio creía que ocurría, no lo que había ocurrido.

Habíamos creado una segunda fuente de verdad y luego gastábamos tiempo reconciliándola con la primera.

La frase que más repetía en los informes era esta: el código estaba bien y el estado estaba mal.

Dónde vive el estado ahora

El pipeline ya no guarda un estado propio. Lo toma prestado.

La cola de tareas es GitHub Issues. Un issue abierto con la etiqueta de cola es la cola. El ciclo de vida también son etiquetas: reclamado, listo para QA, validado, bloqueado. Terminado no es una etiqueta. Es cerrar el issue. El código y su historia viven en git. La versión publicada es la que informa la plataforma de despliegue, y nosotros preguntamos en lugar de recordarla.

Las tres herramientas existían antes del pipeline. Una persona puede inspeccionarlas desde un navegador y sobreviven a cualquier caída nuestra porque no nos pertenecen.

Una ejecución no es un paso guardado en un flujo. Es una función del mundo: lee todo, decide cuál es la siguiente parte limitada, la hace y termina.

Por eso, repetirla cuesta poco. Si una sesión muere a la mitad por un proceso detenido, una laptop cerrada o un token vencido, la siguiente lee el mundo y sigue. No hay un protocolo para reanudar porque no existe una posición interna que recuperar. Ejecutarla con la cola vacía no hace nada.

Los fallos se recuperan del mismo modo. Si una tarea falla, su etiqueta vuelve a la cola y la sesión deja un comentario con lo que se rompió. El fallo ya forma parte del estado del mundo. La siguiente sesión lo ve y recibe ese comentario como contexto.

La idempotencia no sale gratis

Aquí tengo que ser honesto. La frase “cada ejecución es una función pura del mundo” suena preciosa, pero nos costó dos incidentes reales.

Durante un tiempo, una ejecución podía crear un segundo agente con el mismo identificador. Ambos leían el mismo mundo y ambos concluían correctamente que una tarea no tenía dueño. Aparecían dos pull requests con la misma tarea y casi el mismo código. Las lecturas estaban bien. El problema era que leer y reclamar no ocurrían en un solo paso. La reclamación era decorativa.

Añadimos un mutex, una llave que la ejecución debía tomar antes de tocar una tarea. Eso produjo el segundo incidente y todavía me parece gracioso.

Las ejecuciones suelen trabajar dentro de copias aisladas del repositorio. El mutex vivía dentro de esa copia. Cada sesión recibió su propia versión privada de la llave, la tomó con éxito y siguió adelante. Una llave que siempre abre equivale a no tener llave, pero con pasos extra. El vigilante tenía el problema opuesto. Leía un registro vacío en una copia nueva y anunciaba que la cola estaba detenida cuando en realidad seguía avanzando.

La corrección cabe en una línea de intención: localizar el repositorio compartido y guardar allí el registro y el mutex. Un registro y una llave, sin importar cuántos espacios aislados existan. Si el estado usado para coordinar se puede copiar por accidente, no sirve para coordinar.

El desorden y la reparación inicial

Las etiquetas se desordenan. Dos sesiones coinciden, una persona edita un issue o una herramienta externa mueve algo. Si nadie corrige eso, aparecen combinaciones sin sentido: una tarea puede estar en cola y validada al mismo tiempo, o en progreso sin ninguna ejecución viva.

La primera acción de cada sesión es reconciliar el estado. Dos reglas resuelven casi todo. Cerrar gana porque un issue cerrado es la señal más fuerte. Cuando hay etiquetas contradictorias, elegimos el estado menos avanzado.

Esa segunda regla es pesimista a propósito. Si no sabemos si algo fue validado, la lectura segura es que no lo fue. Bajar de estado puede repetir trabajo. Subir de estado puede publicar algo que nadie revisó.

Nadie lee el log

Una sesión sin interfaz que falla de noche produce un log que ninguna persona abrirá.

Por eso el pipeline no deja nada importante solo en logs. Cada sesión termina publicando los problemas pendientes en un único issue fijo. Los deduplica con una huella para que un problema que dura tres días sea un comentario visible, no nueve copias que nadie recorrerá.

La mañana empieza con una lista corta en una página. Esa es toda nuestra historia de monitoreo. Seis semanas después, todavía no quiero un tablero.

Lo que sacrificamos

Velocidad inmediata. Una tarea que termina al mediodía espera hasta la validación de la tarde y la promoción nocturna. Un daemon podría haber hecho las tres cosas en veinte minutos.

Pagamos ese costo con gusto porque compramos algo más valioso. Ningún fallo del pipeline puede crear un estado que una persona no pueda leer en un navegador y arreglar cambiando una etiqueta. El diseño que descarta una ejecución completa lleva décadas aplicándose a servidores. Funciona igual de bien con agentes y hace más falta. Los fallos de un agente son más extraños que los de un servidor. Poder decir “descarta la sesión, el mundo sigue correcto” importa todavía más cuando lo que falló podía ser creativo.

La siguiente nota trata de los controles. Explica dónde convertimos el juicio humano en reglas para que los agentes puedan trabajar sin supervisión y por qué producción es la parte menos inteligente de todo el sistema.