Saltar al contenido
← Volver a Novedades

Ingeniería11 min de lectura

n8n cae con «JavaScript heap out of memory» en los nodos agente

El contenedor muere a mitad de ejecución y vuelve sin más explicación que una línea del log. Esa línea te dice cuál de dos problemas completamente distintos tienes — y tienen arreglos opuestos.

Una instancia de n8n auto-hospedada que llevaba meses estable empieza a reiniciarse. El flujo es el mismo de siempre, salvo que ahora lleva un nodo agente de IA, y a veces un bucle alrededor de ese nodo. Los logs terminan con una traza que tú no escribiste, y el contenedor vuelve como si nada — hasta la siguiente ejecución.

Lo primero no es arreglarlo. Es averiguar cuál de dos fallos distintos estás mirando, porque el consejo estándar para uno empeora el otro.

Leer el error FATAL de heap en los logs de n8n

Hay dos formas de que un contenedor de n8n muera por memoria, y se parecen desde fuera y no tienen nada que ver por dentro.

<--- Last few GCs --->
[1:0x7f8b2c0] 412039 ms: Mark-sweep 3969.1 (4066.5) -> 3968.3 (4067.2) MB

<--- JS stacktrace --->

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
Caso A: V8 llegó a su propio techo. El proceso se mató a sí mismo y lo dijo.
$ docker inspect n8n --format '{{.State.ExitCode}} {{.State.OOMKilled}}'
137 true
Caso B: lo mató el kernel desde fuera. No hay traza porque el proceso nunca tuvo ocasión de escribirla.

La distinción decide tu siguiente movimiento. En el caso A al proceso le quedaba memoria según el sistema operativo, pero V8 se negó a crecer más allá del límite de old-space que tiene configurado. En el caso B el contenedor tocó el límite de memoria de su cgroup y lo terminaron — sin aviso, sin traza, exit 137.

Por qué los nodos agente retienen memoria

n8n mueve los datos entre nodos como un array de items en memoria. Ese diseño es lo que hace que el editor se sienta inmediato —puedes inspeccionar la salida de cualquier nodo cuando quieras— y es también por lo que el perfil de memoria de un flujo es más o menos la suma de todo lo que ha producido cada nodo, no el tamaño de lo que está procesando el nodo actual.

Un nodo agente apila varios multiplicadores encima de eso:

  • Memoria de conversación. Una ventana de búfer mantiene vivos los últimos N intercambios para que el agente tenga contexto. N cuenta turnos, no bytes — una ventana de 10 es pequeña con mensajes cortos y enorme en cuanto una herramienta empieza a devolver documentos.
  • Las salidas de las herramientas entran en el contexto. Todo lo que devuelve una herramienta se añade a la conversación para que el modelo pueda razonar sobre ello. Una herramienta que devuelve la respuesta completa de una API mete esa respuesta entera en memoria, y la deja ahí el resto de la ejecución.
  • La salida de los sub-nodos se retiene. Cada sub-nodo colgado del agente guarda su propia salida para poder inspeccionarla en el editor, así que los pasos intermedios no desaparecen cuando el agente sigue adelante.
  • Los datos binarios van a memoria por defecto. Si no pones el modo de datos binarios en filesystem o S3, los adjuntos y las descargas viven en el heap junto a todo lo demás.
  • Los bucles multiplican todo lo anterior. Ejecutar el agente una vez por fila de una hoja de 500 filas no son 500 ejecuciones pequeñas: es una ejecución que acumula 500 contextos de agente.

Por eso la caída correlaciona con el volumen de datos y no con la complejidad del flujo, y por eso aparece semanas después de haberlo construido: la lógica no cambió, las entradas simplemente crecieron.

Bucles de reinicio en auto-hospedado

El reinicio es lo que convierte una ejecución mala en una tarde mala. Con una política de reinicio puesta, el contenedor vuelve, n8n retoma lo que puede, y si el dato que lo disparó sigue encolado vuelve a correr la misma ejecución condenada. Tres o cuatro ciclos después, el cuadro es un servicio que técnicamente está arriba y no está haciendo nada.

Cosas que conviene poner antes de tocar nada arquitectónico — ninguna es un arreglo, son la forma de dejar de perder información sobre el fallo:

AjusteQué hacePor qué importa aquí
N8N_DEFAULT_BINARY_DATA_MODE=filesystemSaca los adjuntos del heapElimina el mayor contribuyente único en flujos con ficheros
EXECUTIONS_DATA_SAVE_ON_SUCCESS=noneDeja de guardar el payload de las ejecuciones correctasEncoge la base de datos y lo que se retiene durante la ejecución
EXECUTIONS_DATA_PRUNE=trueCaduca los datos de ejecuciones viejasCorta una fuga lenta que parece una fuga de memoria
EXECUTIONS_MODE=queueLas ejecuciones corren en workers aparteUna caída mata un worker, no el editor y los demás flujos
Límite del contenedor ≥ límite de heap + margenAlinea los dos techosConvierte las muertes silenciosas por exit 137 en errores de JS legibles
El modo cola es lo más rentable de esta lista, y sigue siendo contención y no cura: una ejecución que acumule de más agotará igualmente su worker.

Esa última fila es la que todo el mundo se salta. Si el contenedor está limitado a 2 GB y V8 está configurado para 4 GB, V8 no llegará nunca a su propio límite — el kernel llega antes, y tú no ves jamás el error FATAL que te habría contado lo que pasaba.

Estado externo y contexto por agente

Todas las mitigaciones de arriba van de sobrevivir a la acumulación. El arreglo estructural es no acumular: el contexto del agente deja de ser una variable dentro de un proceso y pasa a ser un registro fuera de él.

Eso cambia lo que significa una caída. Cuando el historial de conversación, los resultados intermedios y las salidas de herramientas viven en un almacén externo, el proceso que atiende un paso solo sostiene ese paso. Se le puede reiniciar, mover a otra máquina o correr a la vez que otros cien, porque no hay nada dentro que merezca la pena conservar.

También cambia lo que significa un bucle. Quinientas filas pasan a ser quinientas unidades de trabajo independientes con su contexto aislado, en vez de una ejecución cuya huella de memoria crece con cada fila. El fallo deja de ser todo o nada: la fila 340 falla, las filas 1 a 339 ya están hechas, y la 340 se reintenta sola.

Así corren los agentes en Bentho: contexto persistente por agente en estado externo, ejecutado por workers que sostienen una unidad de trabajo cada vez. No es un parámetro de tuning, y ese es justo el punto: no hay ningún valor de --max-old-space-size que haga que un diseño en proceso deje de acumular.

Ruta de migración para los flujos críticos

Nadie debería migrar de golpe una instancia de n8n que funciona, y no hace falta. Los flujos que caen son un subconjunto pequeño —normalmente los que tienen un agente dentro de un bucle— y son los que vale la pena mover primero.

  1. Confirma qué fallo tienes. Mira el código de salida y si OOMKilled es true. Todo lo que viene después depende de esa respuesta.
  2. Compra margen por la vía barata. Datos binarios a filesystem, datos de ejecución podados, modo cola activado. Esto muchas veces para los reinicios del todo, y siempre te da sitio para trabajar.
  3. Encuentra el nodo que acumula. Casi siempre es un agente, un bucle, o un nodo HTTP que devuelve algo mucho más grande de lo que nadie supuso. Registra el número de items y el tamaño del payload entre nodos.
  4. Mueve ese flujo, solo ese. El agente corre como worker de Bentho con estado externo; n8n se queda con los disparadores y las integraciones, que es en lo que es bueno, y llama al worker en vez de hospedar el agente.
  5. Verifica con la carga que lo rompió. Reproduce el volumen real, no una fila de prueba. Un flujo que sobrevive a una fila nunca fue el problema.
  6. Deja el resto en paz. Los flujos que nunca cayeron no necesitan plataforma nueva.

El paso uno cuesta dos minutos y descarta la mitad de los consejos de internet. Empieza ahí, aunque hoy no hagas nada más.

Preguntas frecuentes

¿Qué significa FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory en n8n?

Significa que el proceso de Node.js agotó el heap que V8 tenía configurado y se detuvo a sí mismo. En n8n suele querer decir que una ejecución acumuló en memoria más datos de los que permite el límite — habitualmente un agente de IA dentro de un bucle, o datos binarios retenidos en el heap. No es un fallo de la lógica de tu flujo: es la cantidad de datos que la ejecución sostiene a la vez.

¿El exit code 137 es el mismo problema?

No, y distinguirlos importa. Un exit 137 con OOMKilled=true significa que el kernel mató el contenedor por pasarse de su límite de memoria — no hay traza porque nadie se la pidió al proceso. El FATAL ERROR de heap es V8 deteniéndose solo. Subir --max-old-space-size ayuda al segundo y empeora el primero.

¿Lo arregla poner más RAM?

Lo aplaza. Más RAM sube el techo, y una ejecución cuya memoria crece con el tamaño de su entrada acaba llegando a cualquier techo. Es una medida de emergencia razonable y un mal plan.

¿El modo cola resuelve el problema de memoria de los agentes?

Lo contiene. Las ejecuciones corren en workers separados, así que una mala mata un worker en vez de llevarse por delante el editor y todos los demás flujos. La ejecución que acumula de más sigue muriendo — lo que deja de hacer es arrastrar a todo lo demás.

¿Por qué empezó a pasar sin haber cambiado el flujo?

Porque el uso de memoria sigue al volumen de datos, no a la complejidad del flujo. Un flujo construido contra 20 filas y un historial de conversación corto se comporta de forma completamente distinta contra 2.000 filas y salidas de herramientas que fueron creciendo.

Seguir leyendo