Erreur Code node manque de mémoire lors du parsing de tableaux imbriqués massifs dans les exécutions de boucles haute fréquence

Bonjour à tous, je gère actuellement une instance auto-hébergée de n8n via Docker et je l’utilise pour construire un pipeline de données hautement automatisé. Le workflow récupère un lot dense de chaînes de télémétrie d’exécution système dynamiques à partir d’un environnement sandbox externe, transmet les données brutes dans un nœud Code pour le filtrage regex, puis tente de boucler sur les éléments pour mettre à jour une base de données externe. La mise en page fonctionne bien avec de petites charges utiles de test, mais je me heurte à un goulot d’étranglement critique des performances lors du traitement de volumes au niveau de la production.

Le problème principal est que chaque fois que le workflow rencontre une structure de tableau imbriqué massif de plusieurs mégaoctets, le conteneur n8n lance une erreur de mémoire insuffisante et s’arrête complètement. Cela déclenche une cascade de problèmes imbriqués sur l’ensemble de la configuration : le suivi de l’exécution active se brise, la file d’attente se remplit de tentatives, et la base de données SQLite locale se verrouille complètement jusqu’à ce que je force manuellement le redémarrage du conteneur via SSH. Je tente de déterminer si le nœud Code JavaScript interne ne parvient pas à libérer la mémoire pendant l’exécution parce qu’il traite l’ensemble de la charge utile dans un seul bloc, ou si les paramètres de journalisation historique des données d’exécution de n8n gonflent simplement l’utilisation de RAM du conteneur au-delà de ses limites allouées.

J’expérimente avec un modèle de gestion personnalisée de la mémoire que j’ai obtenu comme téléchargement gratuit pour voir si le fractionnement explicite du tableau JSON entrant en paquets de chunks plus petits avant qu’il ne frappe le moteur d’analyse empêche le système de s’étouffer. Malheureusement, même en divisant les données en micro-lots, les boucles de fond continu causent toujours une fuite mémoire lente qui tue inévitablement le thread de travail du workflow après quelques heures d’automatisation continue. Je tente de déterminer s’il est plus judicieux de décharger complètement la mécanique lourde d’analyse de données vers un microservice externe via un nœud Webhook, ou s’il existe une variable d’environnement que je peux configurer dans mon fichier compose pour forcer un garbage collection agressif entre les boucles de workflow actives.

Si quelqu’un d’autre ici a conçu des boucles de données à grand volume ou géré des tableaux volatiles générés par des scripts à l’intérieur de nœuds auto-hébergés sans déclencher des délais d’expiration de conteneur ou des pics de mémoire, comment optimisez-vous vos tampons de données ? Je serais incroyablement reconnaissant pour tout conseil de structure de workflow ou configurations de performance pour maintenir ce pipeline stable.

Salut @Ivonne566 Bienvenue!
C’est le nœud Code qui retient chaque chunk traité dans le même tas V8 tout au long de l’exécution qui fuit, pas la journalisation de l’historique d’exécution. Le chunking manuel dans une seule exécution de nœud Code ne libère pas cette mémoire entre les itérations, seule la fin de l’exécution le fait, et il n’y a aucune variable d’environnement qui force la GC au milieu de la boucle. Utilise un nœud Loop Over Items pour regrouper le tableau par lot, puis remets chaque lot à un sous-workflow via Execute Workflow. Chaque lot devient sa propre exécution qui se termine et libère la mémoire avant le suivant, et le workflow parent ne conserve que le petit résultat retourné.
En attendant de redesigner, augmente le tas V8:

NODE_OPTIONS=--max-old-space-size=<SIZE_IN_MB>

Cela ne fait que relever le plafond, la fuite de la même exécution réapparaît une fois que le payload dépasse ce seuil.

Si le verrouillage SQLite continue de se produire une fois l’arrêt résolu, passe à PostgreSQL, SQLite n’est pas conçu pour les écritures concurrentes qu’une file d’attente de retry produit.

Pour répondre à la deuxième partie de votre question (« les paramètres de journalisation de l’historique des données d’exécution gonflent-ils simplement l’utilisation de la RAM du conteneur »), puisqu’elle n’a pas encore été abordée :

L’historique d’exécution exerce surtout une pression sur votre base de données, pas sur le tas du worker. Pendant qu’une exécution est en cours, les éléments qui circulent dans le workflow sont conservés en mémoire quel que soit le paramètre de sauvegarde — c’est le plafond de tas V8 qu’Anshul a couvert. Mais lorsque l’exécution se termine, n8n écrit la charge d’exécution entière dans la base de données. Avec des tableaux imbriqués multi-MB sur SQLite, c’est ce qui gonfle le fichier de base de données et aggrave la contention de verrous et l’arriéré de file d’attente que vous avez décrit.

Quelques paramètres à vérifier :

  • EXECUTIONS_DATA_SAVE_ON_SUCCESS=none (et gardez EXECUTIONS_DATA_SAVE_ON_ERROR=all pour pouvoir toujours déboguer les défaillances) — empêche l’écriture de la charge énorme pour chaque exécution réussie.
  • L’élagage est activé par défaut, mais vérifiez EXECUTIONS_DATA_MAX_AGE (336 heures par défaut = 14 jours). Avec des charges de votre taille, le raccourcir garde SQLite bien plus petit. EXECUTIONS_DATA_PRUNE_MAX_COUNT (10 000 par défaut) est le plafond complémentaire.
  • Laissez EXECUTIONS_DATA_SAVE_ON_PROGRESS désactivé sauf si vous avez vraiment besoin d’une reprise en cas de panne — sauvegarder la progression en cours d’exécution multiplie les écritures de base de données.

Aucun de cela ne corrige l’OOM lui-même — le batching Loop Over Items + sous-workflow d’Anshul est le vrai correctif. Mais cela supprime la pression de base de données composée, et si vous migrez vers Postgres comme suggéré, les mêmes paramètres maintiennent cette base de données en bonne santé aussi.

Merci de nous l’avoir signalé, nous avons créé CV-15 comme ticket de développement interne pour l’examiner.

Bonjour @Ivonne566,
Quel est le nombre de lignes que vous traitez ? et la structure (champs) + la taille moyenne de chaque ligne ?