Comment empêcher les workers n8n de traiter le même job PostgreSQL simultanément ?

BONJOUR À TOUS
J’exécute plusieurs workers n8n qui récupèrent les tâches en attente de la même table PostgreSQL. Sous un trafic normal, ça fonctionne, mais quand plusieurs workers interrogent la table en même temps, la même tâche peut parfois être récupérée par plus d’un worker.
Je fais actuellement :
SELECT id, payload
FROM jobs
WHERE status = ‘pending’
ORDER BY created_at
LIMIT 10;
Ensuite, je mets à jour les tâches sélectionnées en cours de traitement.
J’envisage de changer cela à :
SELECT id, payload
FROM jobs
WHERE status = ‘pending’
ORDER BY created_at
FOR UPDATE SKIP LOCKED
LIMIT 10;
Quel serait le meilleur modèle de production pour une attribution atomique de tâches dans n8n + PostgreSQL ?
Devrais-je utiliser FOR UPDATE SKIP LOCKED, un UPDATE … RETURNING atomique, ou une combinaison des deux ?
En outre, comment devrais-je gérer les tâches quand un worker plante après les avoir revendiquées pour qu’elles puissent être retentées en toute sécurité sans créer de traitement en double ?

Décrivez le problème/l’erreur/la question

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 la sortie renvoyée par le dernier nœud

Informations sur votre configuration n8n

  • Version de 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 :

Je ferais les deux, juste pour être sûr, quelque chose comme ça pourrait aider !

Salut @Mary_Berry Pour cette situation, utilise FOR UPDATE SKIP LOCKED ou un UPDATE … RETURNING atomique, avec un délai d’expiration du bail pour les workers qui se sont plantés.

L’objectif principal est de rendre l’attribution d’un job atomique, afin que deux workers n8n ne puissent pas récupérer le même job.

Attribuer les jobs de manière atomique
Une bonne approche est :
WITH jobs_to_claim AS (
SELECT id
FROM jobs
WHERE status = ‘pending’
ORDER BY created_at
FOR UPDATE SKIP LOCKED
LIMIT 10
)
UPDATE jobs
SET
status = ‘processing’,
locked_at = NOW(),
worker_id = $1
FROM jobs_to_claim
WHERE jobs.id = jobs_to_claim.id
RETURNING jobs.*;

Si un worker se plante, un autre worker peut éventuellement réattribuer le job.

Protéger contre les workers obsolètes

Lors de la fin d’un job, vérifie que le worker en est toujours propriétaire :
UPDATE jobs
SET status = ‘completed’
WHERE id = $1
AND status = ‘processing’
AND worker_id = $2;

Si cela met à jour 0 lignes, ce worker n’est plus propriétaire du job et ne devrait pas le modifier.

Le principe clé est : la base de données doit contrôler la propriété du job, pas les workers n8n eux-mêmes.

Bonjour @Mary_Berry Bienvenue !
En mode file d’attente, le doublon peut provenir de n8n lui-même, avant l’exécution de votre requête de réclamation. Chaque worker détient un bail Redis sur l’exécution pendant QUEUE_WORKER_LOCK_DURATION (60000 ms) et le renouvelle toutes les QUEUE_WORKER_LOCK_RENEW_TIME. Un worker qui s’arrête ou qui est simplement trop occupé pour renouveler à temps voit son exécution marquée comme bloquée et redirigée vers un autre worker, qui réexécute le workflow à partir du déclencheur et réclame un nouveau lot tandis que le lot arrêté reste dans processing. Sur n8n 1.x, cette relecture se produit une fois par défaut, donc désactivez-la et définissez le bail au-dessus de votre lot le plus lent :

QUEUE_WORKER_MAX_STALLED_COUNT=0
QUEUE_WORKER_LOCK_DURATION=300000

Sur n8n 2.0, cette variable et la tentative Bull stalled ont tous deux disparu, donc supprimez la première ligne après la mise à niveau et récupérez les lignes concernées à partir de votre propre table.