Stratégie de gestion de la contention des ressources entre locataires dans une architecture multi-tenant à grande échelle

Bonjour à tous,
J’exécute une plateforme d’automatisation multi-locataire basée sur n8n et j’ai commencé à observer des problèmes où quelques locataires à haut volume affectent l’expérience de tous les autres.
Architecture actuelle :
File d’attente partagée
↓
Pool de workers partagé
↓
Infrastructure partagée
Problèmes observés :
• Un seul locataire peut surcharger la file d’attente
• Les workflows de longue durée retardent les tâches plus petites
• Les locataires lourds en API consomment une capacité de workers disproportionnée
• Latence accrue pour les locataires à faible volume
Mes objectifs :
• Allocation équitable des ressources entre les locataires
• Prévenir les problèmes de voisins bruyants
• Maintenir des performances prévisibles
• Échelle sans surprovisionnement d’infrastructure
J’envisage :
• Files d’attente par locataire
• Files d’attente prioritaires
• Pools de workers par niveau de locataire
• Limitation de débit et quotas
• Infrastructure dédiée pour les locataires d’entreprise
Pour les équipes exploitant des plateformes de workflows multi-locataires à grande échelle :
Comment prévenez-vous les problèmes de voisins bruyants ?

Quel est le message d’erreur (le cas échéant) ?

Veuillez partager votre workflow

(Sélectionnez les nœuds sur votre canevas et utilisez les raccourcis clavier CMD+C/CTRL+C et CMD+V/CTRL+V pour copier et coller le workflow.)

Partagez le résultat retourné par le dernier nœud

Informations sur votre configuration n8n

  • Version n8n :
  • Base de données (par défaut : SQLite) :
  • Paramètre n8n EXECUTIONS_PROCESS (par défaut : own, main) :
  • Exécution de n8n via (Docker, npm, n8n cloud, application de bureau) :
  • Système d’exploitation :

Salut @Keira_Becky L’approche la plus courante consiste à combiner les limites de débit, l’isolation des files d’attente et l’isolation des workers.

File d’attente partagée + Limites par locataire + Workers dédiés pour les locataires importants

Cela empêche un seul locataire de consommer toutes les ressources disponibles.

Stratégies courantes
Appliquer des limites de débit et des quotas par locataire
Utiliser des files d’attente prioritaires pour différents niveaux de service
Router les locataires à volume élevé vers des files d’attente ou des workers dédiés
Conserver les petits locataires sur l’infrastructure partagée

Pour les locataires de grande taille, de nombreuses équipes utilisent : Enterprise Tenant → File d’attente dédiée → Pool de workers dédiés

Assurez-vous d’éviter
Une file d’attente globale unique sans limites
Traiter tous les locataires de la même manière indépendamment de l’utilisation
Permettre des tentatives illimitées ou des tâches longues sur des workers partagés

La solution la plus propre pour le problème de noisy-neighbor est d’arrêter de partager la ressource sur laquelle les tenants se battent. Au lieu d’une seule file d’attente partagée et d’un pool de workers avec des limites ajoutées après coup, exécutez une instance n8n par tenant : sa propre file d’attente, ses propres workers, sa propre base de données et clé de chiffrement, son propre conteneur. Le noisy-neighbor cesse d’être quelque chose que vous accordez et devient structurellement impossible, car il n’y a pas de file d’attente partagée qu’un tenant pourrait inonder et pas de pool partagé qu’un tenant gourmand en API pourrait affamer.

L’approche file-d’attente-partagée-plus-limites (limites de débit par tenant, niveaux de priorité, workers dédiés pour les gros consommateurs) fonctionne, et c’est le bon choix si vous êtes coincé dans une grosse instance unique. Mais vous vous engagez à accorder à jamais les limites de débit, les poids de priorité, les plafonds de tentatives et la concurrence, et une seule valeur mal définie ou un seul workflow pathologique provoque encore des fuites entre tenants. L’ordonnancement équitable sur un pool partagé est une cible mouvante que vous ne cesserez jamais de poursuivre.

Le compromis honnête avec instance-par-tenant est l’inverse : rayon de blast quasi nul, mais plus d’infrastructure de base inactif et plus de choses à exploiter. N instances à déployer, sauvegarder, surveiller et mettre à jour. Ce coût opérationnel est la vraie objection, et c’est celle-ci qui est résoluble :

  • Limitez chaque conteneur. Limites de CPU et de mémoire par instance (compose ou k8s) pour que même un workflow en fuite soit borné à son propre tenant, pas à l’hôte.
  • Isolez l’exécution aussi. Exécutez les nœuds Code dans un sidecar task-runner externe par instance, afin que l’exécution JS/Python soit contenante, pas seulement la file d’attente.
  • Échelonnez-le. Vous n’avez pas besoin d’une instance par tenant dès le jour un. Les petits tenants peuvent partager une instance ; accordez-en une propre aux tenants lourds et d’entreprise. Même le niveau partagé est plus sain fragmenté sur quelques instances qu’une seule file d’attente globale.
  • Mettez un plan de contrôle au-dessus de la flotte. Au-delà d’une poignée d’instances, vous avez besoin d’un endroit unique pour voir les erreurs, la santé et les exécutions sur toutes, et pour déployer et sauvegarder sans vous connecter en SSH d’une boîte à l’autre.

Bienvenue @Keira_Becky!

Le levier le plus direct en mode queue n8n est EXECUTIONS_CONCURRENCY_ACTIVE - à définir par conteneur worker, pas globalement. Pour le problème du voisin bruyant, le modèle qui fonctionne en pratique est : exécuter 2-3 niveaux de workers avec des plafonds de concurrence différents, puis router les gros clients vers leur propre groupe de workers en utilisant un préfixe de clé de queue Redis séparé (à définir via QUEUE_BULL_PREFIX par worker). Les clients à gros volume obtiennent un worker dédié avec un EXECUTIONS_CONCURRENCY_ACTIVE plus bas (par exemple 5), le niveau partagé obtient plus de workers mais chacun limité à 10. Les workflows de longue durée sont un cas plus difficile - utilisez EXECUTIONS_TIMEOUT au niveau de l’instance et EXECUTIONS_TIMEOUT_MAX par workflow pour empêcher une seule exécution de bloquer un worker indéfiniment. Si deux ou trois clients maxent régulièrement leur propre queue, les upgrader vers une instance n8n dédiée est généralement plus propre que de chercher à les isoler à l’intérieur d’un cluster partagé unique.

Nous avons eu le même problème sur une plateforme n8n+Postgres multi-locataire plus tôt cette année — des appels LLM dépassant les TTL Redis, un locataire lent bloquant tout le monde.

Ce qui a vraiment arrêté l’hémorragie : des préfixes de file d’attente BullMQ par niveau de locataire (QUEUE_BULL_PREFIX) + des plafonds EXECUTIONS_CONCURRENCY_ACTIVE séparés par pool de workers. Le verrou Redis est le chemin rapide. Puis une contrainte UNIQUE Postgres (tenant_id, message_id) avec ON CONFLICT DO NOTHING comme porte d’idempotence durable — c’est la couche de vérité quand les appels Gemini dépassent le TTL Redis.

À vérifier de votre côté : votre TTL de verrou Redis est-il plus long que votre P99 d’appel LLM ? C’est généralement là que commence l’hémorragie d’exécutions en doublon.

Aussi — si p95 LLM reste sous 2 s et pool_wait est plat, la contention est ailleurs. Généralement le churn QUEUE_RECOVERY_INTERVAL sur le planificateur.