Lo que queríamos hacer era muy sencillo.
Escribir un artículo nuevo y publicarlo lo antes posible.
Nada más.
Pero, sin darnos cuenta, antes de publicar ya había empezado una reunión.
¿A quién le toca esta vez? ¿A Workers o a Actions? ¿Queda presupuesto? ¿Sigue activo el bloqueo anterior? ¿De quién es la marca? ¿Cuántas veces hemos reintentado ya?
El artículo espera.
El sistema también espera.
Todos piensan: "esto no me toca a mí".
En una empresa puedes llamar a todos a una sala. Un sistema distribuido, en cambio, se detiene en silencio.
Eso es el infierno de Live Schedule.
1. Queríamos publicar al instante y acabamos haciendo elecciones para elegir responsable
Cuando intentas hacer más fuerte el proceso de publicación, es natural añadir más seguros.
Si Workers se detiene, pasa a Actions. Si Actions anda justo de presupuesto, pasa a Workers. Pon un bloqueo para que nadie haga el mismo trabajo dos veces. Pon una marca para que dos procesos no toquen la misma tarea. Cuenta los reintentos. Añade a alguien que vigile.
Mirada una por una, todas son buenas ideas.
El problema aparece cuando las pones en fila, una detrás de otra.
Antes de "¿se puede publicar este artículo?", te toca resolver algo mucho más grande: "¿quién tiene permiso para publicarlo?".
Lo más peligroso es cuando solo sobrevive la etiqueta del responsable.
El proceso de Workers está parado. Pero el registro sigue diciendo "a cargo de Workers". Cuando Actions se despierta, lee "a cargo de Workers" y se salta la tarea.
Nada está roto. Nadie está trabajando.
Es la versión de un sistema distribuido de "la persona encargada no está en su puesto".
2. El fallo era de coordinación, no de capacidad para ejecutar
En una de las ejecuciones, Actions todavía tenía capacidad y presupuesto.
Aun así, el trabajo principal no se ejecutó.
La razón es simple: la comprobación decía que "esa tarea es de Workers".
Aquí está lo importante.
No faltaba CPU. No estaban caídas todas las API. Actions podía arrancar sin problema.
El problema es la coordinación, no la ejecución.
Lo que frena todo es decidir quién lo hace, no la fuerza para hacerlo.
Así, arreglar errores sueltos no termina el problema.
Arreglas cuántos datos se descargan. Arreglas el fallo al guardar en main. Arreglas los reintentos. Arreglas el cambio de responsable. Y ahora queda una marca vieja. Después queda un bloqueo viejo. El vigilante detecta el problema, pero no tiene permiso para publicar.
Parece el juego de golpear topos, pero todos salen del mismo agujero.
La capa de control es demasiado lista.
3. Un schedule es un reloj, no una lista de tareas
El schedule de GitHub Actions es práctico, pero la documentación oficial dice que puede retrasarse cuando hay mucha carga y que, si la carga es muy alta, parte de los trabajos en cola puede perderse.
Por eso, un schedule sirve para "despiértame más o menos a esta hora".
Pero no sirve para "alguien tiene que recoger este texto y quedárselo hasta que salga bien".
Un reloj suena.
Un reloj no guarda tu texto.
Una cola de mensajes, en cambio, guarda el trabajo mismo. También sabe manejar fallos: reintentos, esperas y una "cola de mensajes fallidos" (dead-letter queue), que es un lugar aparte para lo que sigue fallando.
Esto no quiere decir que haya que añadir un servicio nuevo.
Quiere decir repartir bien los papeles:
- schedule = lo que despierta cada cierto tiempo
- lista de pendientes = el registro oficial del trabajo sin terminar
- ejecutor = quien hace el trabajo de verdad
- bloqueo corto = la llave que evita que dos trabajen a la vez
- reintento y cola de fallidos = adónde va lo que falló
Si el reloj también hace de recursos humanos, almacén y objetos perdidos, el reloj va a tener un mal día.
4. Lo que importa es "¿qué falta por terminar?", no "¿quién es el dueño?"
En un reparto complicado de tareas, el registro suele girar alrededor de la persona responsable.
Pero en un proceso de publicación, lo que de verdad quieres saber es:
¿Está publicado este artículo o no?
Casi solo eso.
Por eso el registro oficial puede ser la lista de pendientes.
Entra un texto nuevo: va a pendientes. Al empezar a publicar: se pone un bloqueo corto. Si sale bien: se marca como publicado. Si falla: vuelve a pendientes. Si falla demasiadas veces: pasa a la cola de fallidos y salta un aviso.
"A cargo de Workers" o "a cargo de Actions" es solo información de apoyo. Nunca debe ser un veto permanente.
Aunque diga "a cargo de Workers", si pasa un tiempo sin avance, otro ejecutor puede recoger la tarea.
Así, una etiqueta de responsable muerta no puede tener el trabajo como rehén.
5. La vía principal arranca con el evento; el schedule solo recoge lo que se escapó
Si quieres publicar al instante, esperar al siguiente aviso programado va contra el propósito.
Despierta al ejecutor en cuanto llega un texto nuevo.
Esa es la vía principal.
Y el schedule recoge los pendientes viejos que siguen ahí pasados unos minutos o unas decenas de minutos.
Es decir:
El evento es la vía principal. El schedule es el seguro.
Así, un día normal todo va rápido.
Si se pierde un aviso, el schedule lo recoge después.
Si pones el schedule como vía principal, cualquier retraso retrasa la publicación. Y si encima añades la comprobación de responsable, aparece el "se despertó, pero no hizo nada".
Sonó la alarma.
Pasó la comprobación.
Y dijo: "hoy no me toca" y se fue a casa.
¿A qué vino entonces?
6. El respaldo no es "cambiar de responsable", sino "recoger lo viejo sin terminar"
Una trampa común al diseñar el respaldo es pasar el trabajo de A a B de manera formal.
Un traspaso necesita actualizar el registro.
Si esa actualización falla, el propio traspaso se convierte en el problema.
Puede ser mucho más sencillo.
Normalmente Workers hace el trabajo. Si sale bien, se acabó. Si falla, el elemento sigue en pendientes.
Un ejecutor de respaldo, como Actions, revisa cada cierto tiempo. Si
- hay un pendiente
- tiene más antigüedad que un tiempo fijado
- no hay un bloqueo vigente
- él puede ejecutarlo
lo recoge.
No necesita un "certificado de traspaso" de Workers.
Lo que cuenta es el hecho de que el trabajo no está terminado.
Así, que exista el resultado pesa más que el orden de la tabla de responsables.
Para un sistema que publica artículos, es lo más natural.
7. No hace falta tirar Live Schedule, pero no lo conviertas en soporte vital
El problema no es Live Schedule en sí.
Es muy útil cuando una persona está mirando:
Cambiar prioridades. Investigar fallos. Preparar ideas de mejora. Acelerar reparaciones. Meter planes nuevos.
El problema es que, cuando se detiene, también se detiene la fábrica de artículos.
Live debe ser un acelerador, no un punto único de fallo.
Aunque Live no esté,
texto nuevo → pendientes → ejecutor → revisión → publicación
debe seguir funcionando solo.
Si Live está, va más rápido.
Si no está, no se muere nada.
Es como no conectar el corazón de una persona a que alguien conteste en Slack.
8. Cambia la meta de "un sistema listo y sofisticado" a "entra uno, sale uno"
Este tipo de sistema se puede mejorar para siempre.
Reparto justo de tareas. Aprovechar al máximo el plan gratuito. Evitar duplicados. Prever el presupuesto. Prioridades. Vigilancia. Recuperación automática. Análisis.
Todo eso es divertido.
Y si haces todo eso, no sale ningún artículo.
Por eso la primera meta puede ser mucho más simple.
Entra un texto nuevo y, sin ninguna acción especial, se publica uno.
Si falla, se queda en pendientes. Otro ejecutor lo recoge. Solo lo que falla muchas veces pasa a una persona.
Cuando esta línea sencilla sea estable, ya se pueden devolver las optimizaciones.
Solo queríamos publicar rápido los artículos nuevos y, sin darnos cuenta, estábamos construyendo algo parecido a un sistema casero de consenso distribuido.
Hay días así.
Pero hay una manera de salir.
Corta la complejidad.
El valor de un sistema de publicación no es lo bonita que sea la elección del responsable.
Es que los artículos salgan al mundo.
Conclusión
La raíz del infierno de Live Schedule no es el schedule.
Es haber dado más peso al estado de "quién está a cargo" que al estado de "qué trabajo sigue sin terminar".
Si vuelves a un diseño más simple:
- Usa la lista de pendientes como registro oficial del trabajo sin terminar
- Arranca el trabajo al momento cuando llegue el evento
- Mantén los bloqueos cortos
- Haz que el ejecutor de respaldo recoja lo pendiente que ya es viejo
- Usa el schedule como seguro
- Aparta en la cola de fallidos lo que falla demasiadas veces
- Trata a Live como un acelerador opcional
Con eso basta.
Mejor una fábrica que no se detiene que una fábrica lista.
Empieza por ahí.
- GitHub Docs: Events that trigger workflows — schedule https://docs.github.com/actions/reference/workflows-and-actions/events-that-trigger-workflows
- Cloudflare Workers Docs: Cron Triggers https://developers.cloudflare.com/workers/configuration/cron-triggers/
- Cloudflare Queues Docs: Overview https://developers.cloudflare.com/queues/
- Cloudflare Queues Docs: Dead Letter Queues https://developers.cloudflare.com/queues/configuration/dead-letter-queues/

