Schedule Trigger deja de dispararse silenciosamente después de varios días (Kubernetes + modo queue)

Describe el problema/error/pregunta

Tengo un flujo de trabajo que utiliza un Schedule Trigger y se ejecuta varias veces al día. Después de varios días funcionando correctamente, el flujo de trabajo simplemente deja de activarse. No hay ejecuciones fallidas ni mensajes de error en los registros. El flujo de trabajo permanece activo, pero las ejecuciones programadas ya no se inician.

Durante la solución de problemas, noté que si reduzco la cantidad de ejecuciones programadas por día, el flujo de trabajo continúa funcionando durante algunos días más antes de detenerse nuevamente. Esto me hace pensar que el problema puede estar relacionado con el planificador, el modo de cola o alguna limitación de recursos que se acumula con el tiempo.

¿Alguien ha visto que los flujos de trabajo de Schedule Trigger se detengan silenciosamente en una implementación de Kubernetes que ejecuta en modo cola?

¿Cuál es el mensaje de error (si existe)?

No hay mensaje de error. El Schedule Trigger simplemente deja de activarse.

Por favor comparte tu flujo de trabajo

El problema no es específico de un flujo de trabajo en particular. Ocurre con flujos de trabajo que comienzan con un Schedule Trigger y se ejecutan correctamente hasta que el trigger deja de activarse.

Schedule Trigger → (varios nodos)

Comparte la salida devuelta por el último nodo

No hay salida cuando ocurre el problema porque el flujo de trabajo nunca se inicia. Cuando se ejecuta, el flujo de trabajo se completa correctamente.

Información sobre tu configuración de n8n

  • Versión de n8n: Helm chart n8n versión 1.16.39 (por favor hazme saber si la versión de la aplicación real es más relevante)
  • Base de datos: PostgreSQL (externa)
  • Configuración EXECUTIONS_PROCESS de n8n: Modo cola
  • Ejecutando n8n a través de: Kubernetes usando el Helm Chart de la Comunidad
  • Sistema operativo: Cluster de Kubernetes (nodos Linux)

Configuración relevante

redis:
  enabled: true

worker:
  mode: queue

webhook:
  mode: queue
  url: https://
<my-domain>

db:
  type: postgresdb

externalPostgresql:
  host: 
<external-postgres>

Observaciones adicionales:

  • Los flujos de trabajo se detienen sin errores ni ejecuciones fallidas.
  • Reducir la cantidad de ejecuciones programadas por día retrasa el problema pero no lo elimina.
  • Reiniciar la implementación restaura el Schedule Trigger temporalmente.
  • Redis está habilitado y PostgreSQL es externo.

¿Podría estar relacionado con el modo de cola, la elección de líder o el proceso planificador en una implementación de Kubernetes con múltiples pods?

Hola @thun

Hay un problema documentado (referenciado en los problemas de GitHub #30220 y #30256) donde el Schedule Trigger deja de dispararse silenciosamente después de cierta cantidad de ejecuciones (a menudo alrededor de 700+ ticks para flujos de trabajo de alta frecuencia).

  • El Síntoma: El flujo de trabajo permanece “Activo” en la interfaz, las ejecuciones manuales funcionan perfectamente, pero el disparador automatizado simplemente se detiene.
  • La Causa: Ocurre un error de recursión/desbordamiento de pila en observable-object.ts durante la lógica de activación del disparador.
  • Detalle Crucial: Este error no aparece en los registros de ejecución del flujo de trabajo porque el fallo ocurre en el bucle interno del programador antes de que se cree una ejecución. Solo aparece en los registros stdout/stderr del contenedor n8n Main.

Otro problema conocido (#28423) implicaba que el Schedule Trigger usara una comparación de igualdad estricta (===) para determinar si era momento de dispararse.

  • El Síntoma: Si el proceso main se reinicia o ocurre un cambio de liderazgo exactamente cuando se suponía que un disparador debía activarse, la marca de tiempo lastExecution se vuelve “obsoleta”.
  • La Causa: Debido a la verificación estricta, el programador esperaría el siguiente tick exacto. Si el estado se corrompía o se perdía, el flujo de trabajo podría bloquearse silenciosamente durante días o semanas.
  • La Solución: Esto se actualizó a una verificación de “tiempo transcurrido” (>=) para permitir que el programador se “autocorrija” y se dispare inmediatamente si detecta una ventana perdida.

En modo de cola, el Schedule Trigger es propiedad exclusiva del proceso Main (Líder).

  • Disparadores Fantasma: Si tu gráfico de Helm está configurado para permitir más de una réplica del proceso main (o si un pod antiguo persiste durante una actualización progresiva), puedes encontrar “disparadores fantasma” o conflictos de registro en Redis que eventualmente bloquean el programador.
  • Rol de Worker: Si los pods worker ejecutan accidentalmente el comando n8n start en lugar de n8n worker, intentarán actuar como procesos main, compitiendo por el programador y causando comportamiento errático.

¿Se aplica alguno de estos?

¿Puedes hacer también lo siguiente?

  1. Verificar Registros de Contenedor (No Registros de UI): Inspecciona los registros de tu pod n8n main usando kubectl logs <pod-name>. Busca específicamente la cadena: RangeError: Maximum call stack size exceeded Si la ves, has confirmado el error de observable-object.

  2. Verificar Réplicas del Pod Main: Asegúrate de que tu implementación main esté configurada estrictamente en replicas: 1. En modo de cola, tener múltiples mains sin una configuración robusta de elección de líder a menudo genera inestabilidad del programador.

  3. Verificar Comandos del Worker: Verifica la sección args o command de tus pods worker. Deben estar ejecutando: n8n worker (y NO n8n start).

  4. Actualizar Versión de n8n: Las correcciones para tanto el RangeError como el programador “Autocorrector” fueron implementadas en actualizaciones recientes (específicamente abordadas en versiones alrededor de 2.20.9 y posteriores en la rama @next, y portadas a la rama estable). Como estás en 1.16.39, probablemente estés ejecutando una versión susceptible a estos errores. Actualizar a la versión estable más reciente es la corrección principal recomendada.

¡Gracias por la respuesta!

Visualisé los registros principales del pod y vi muchos errores de “Maximum call stack size exceeded”. Me di cuenta de que estoy varios versiones atrás, intentaré actualizar a versiones más nuevas y ver si eso ayuda.

¡Gracias!

Bien que hayas confirmado el ‘Maximum call stack size exceeded’ - ese es el bug del #30220. Una cosa que debes verificar dos veces en tus valores de Helm antes y después de actualizar: replicaCount para el deployment principal debe ser exactamente 1. Ejecutar más de 1 réplica principal causa conflictos de registro del planificador en Redis que pueden reproducir el mismo síntoma de parada silenciosa incluso después de actualizar. Los workers pueden escalar libremente, pero el principal se mantiene en 1.

Me alegra que el grep del log lo haya confirmado — una pared de “Maximum call stack size exceeded” en el pod principal es el bug de acumulación de ticks #30220, y coincide exactamente con tus propias pistas: menos ejecuciones/día = más tiempo para alcanzar el límite, y un reinicio reinicia el contador a cero. La actualización es la solución correcta, y la comprobación replicaCount: 1 de nguyenthieutoan vale la pena hacerla en el mismo paso. Una pequeña cosa mientras eliges a qué versión actualizar: “1.16.39” es la versión del chart de Helm, no la versión de la app n8n — están numeradas de forma independiente, así que verifica a qué se resuelve realmente tu image.tag y apunta a la última versión estable de la app, no a un número de chart.

La parte que marcaría porque persiste después de la actualización: esta clase de fallo es invisible para n8n por diseño. Nunca se crea una ejecución, así que el Error Trigger nunca se dispara — no hay nada a qué adjuntar un error. Por eso funcionó silenciosamente durante días. Incluso completamente parcheado, un scheduler que “simplemente se detiene, activo en la UI, sin error” es algo que siempre descubrirás tarde, cuando un humano note que faltan datos. La solución duradera es un dead-man’s-switch externo: haz que el workflow programado envíe un heartbeat en cada ejecución exitosa (una clave de Redis con TTL, una fila con timestamp, o un ping a un monitor estilo healthchecks), y haz que algo independiente de n8n alerte cuando el heartbeat se vuelva obsoleto. Eso convierte “detenido silenciosamente durante días” en “alerta en minutos”, sea cual sea la causa raíz la próxima vez.

Si la actualización no lo cura completamente — has confirmado el RangeError, pero si aún se queda silencioso en una versión parcheada con replicas: 1 y los workers realmente ejecutando n8n worker — esa es la rama específica del despliegue, y me encantaría ayudarte a investigarla aquí.

Un seguimiento más antes de que este hilo se cierre, porque hay un detalle específico de Kubernetes que las correcciones anteriores no cubren, y explica por qué este fallo es tan silencioso en tu clúster en particular.

Tu sonda de actividad no puede ver este fallo. El endpoint de salud de n8n responde desde el servidor web, y el servidor web está bien mientras el bucle del programador está bloqueado, así que Kubernetes sigue informando que el pod es saludable y nunca lo reinicia. Por eso el apagón dura días: el único mecanismo incorporado para reiniciar un pod roto lee una señal que no ejercita el componente roto. La salud del proceso y la salud del programador son cosas diferentes, y cada sonda que tienes actualmente mide la primera.

Ya que un reinicio restaura confiablemente el disparador, puedes cerrar la brecha mientras planificas la actualización con un canario: un flujo de trabajo programado trivial que no hace nada más que escribir una marca de tiempo en algún lugar económico cada pocos minutos, una clave de Redis, una fila de Postgres, o un ping a un monitor gratuito como Healthchecks.io. Cuando la marca de tiempo queda obsoleta, el programador está bloqueado sin importar lo que diga el endpoint de salud. Si lo quieres completamente automático, un pequeño CronJob que verifique la frescura del canario e haga un reinicio de implementación del despliegue principal cuando quede obsoleto convierte un apagón de varios días en unos minutos de interferencia, que es una solución provisional aceptable para un bug que solo la actualización realmente arregla.

Dos comprobaciones que vale la pena hacer en el próximo bloqueo, antes de reiniciar, para que la decisión de actualización esté fundamentada. Obtén los registros del pod principal y busca RangeError: Maximum call stack size exceeded, como dijo kjooleng. Y haz las matemáticas de los ticks: tu propia observación de que menos ejecuciones por día retrasa el fallo es exactamente lo que predice un bug de umbral de ticks, así que si las ejecuciones por día multiplicadas por días hasta el fallo resulta aproximadamente constante en tus incidentes, tienes la confirmación sin ni siquiera esperar la línea de registro.