SALUT Je fais fonctionner plusieurs workers n8n qui traitent des jobs depuis PostgreSQL. Les workers revendiquent les jobs en changeant leur statut de pending à processing.
Le problème, c’est qu’occasionnellement un worker plante ou redémarre après avoir revendiqué un job. Le job reste bloqué en processing indéfiniment et n’est jamais repris.
Je pense utiliser un timestamp locked_at pour qu’un autre worker puisse identifier les jobs obsolètes :
SELECT id, payload
FROM jobs
WHERE status = ‘processing’
AND locked_at < NOW() - INTERVAL ‘15 minutes’;
Ensuite je pourrais réinitialiser ces jobs à pending.
Je m’intéresse principalement à un pattern qui gère les plantages de workers de manière sécurisée sans créer de traitements dupliqués.
Décrivez le problème/erreur/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.)
utilise un modèle de bail/battement de cœur plutôt que de simplement supposer que chaque travail de traitement datant de plus de 15 minutes est mort.
Je donnerais à chaque travail revendiqué un horodatage locked_at et de préférence un worker_id. Les workers peuvent mettre à jour périodiquement le battement de cœur pendant le traitement.
Pour la récupération, j’utiliserais une mise à jour atomique pour que deux workers ne puissent pas récupérer le même travail :
UPDATE jobs
SET status = ‘pending’,
locked_at = NULL,
worker_id = NULL
WHERE id = $1
AND status = ‘processing’
AND locked_at < NOW() - INTERVAL ‘15 minutes’
RETURNING id;
Si la requête retourne une ligne, ce worker l’a repris avec succès. Si elle ne retourne rien, un autre worker l’a déjà traité ou le travail n’est plus éligible.
Pour les travaux de longue durée, je préférerais vraiment un battement de cœur à un délai d’expiration fixe :
Revendiquer travail
↓
status = processing
↓
Mise à jour du battement de cœur
↓
Traiter le travail
↓
Marquer comme terminé
Ensuite, j’aurais un petit workflow de récupération qui rechercherait régulièrement les travaux dont le battement de cœur a expiré.
Rendez la propriété du travail explicite au lieu de ne vous fier qu’à l’ancienneté du statut de traitement.
Quand un worker récupère un travail, donnez-lui un worker_id et un délai d’expiration du bail :
UPDATE jobs
SET status = 'processing',
worker_id = $1,
lease_until = NOW() + INTERVAL '15 minutes'
WHERE id = $2
AND status = 'pending'
RETURNING id;
Tandis que le travail s’exécute, le worker peut prolonger son bail :
UPDATE jobs
SET lease_until = NOW() + INTERVAL '15 minutes'
WHERE id = $1
AND worker_id = $2
AND status = 'processing';
Un processus de récupération peut alors rechercher les baux expirés et remettre ces travaux dans la file d’attente.
Claim Job
↓
Lease Active
↓
Process + Renew Lease
↓
Complete
Si le worker plante, le bail expire finalement et un autre worker peut prendre le relais.
Je rendrais aussi le traitement réel du travail idempotent et garderais un compteur de tentatives, afin qu’un travail défaillant de façon répétée puisse finalement passer à une dead-letter queue au lieu de réessayer indéfiniment.
Salut @Mary_Berry
Un bail dans votre tableau ne peut pas se renouveler de la manière que le fait n8n. n8n détient un bail de 60 secondes sur l’exécution et l’augmente toutes les 10 secondes sur une minuterie, mais une exécution est un passage direct à travers les nœuds sans fil d’arrière-plan, donc rien ne touche locked_at tandis qu’un nœud HTTP Request est bloqué. Le renouvellement ne se produit que là où un nœud s’exécute, ce qui signifie une Loop Over Items avec un nœud de renouvellement à l’intérieur. Tout le reste doit être dimensionné plutôt que de battement cardiaque.
Le dimensionner signifie concilier deux horloges. Celle qui décide que votre worker est mort est celle de n8n, pas la vôtre, et un worker trop occupé pour se renouveler en moins de 60 secondes voit son exécution déclarée bloquée alors que la ligne est toujours marquée comme vôtre. Sur 2.x rien ne réexpédie cette exécution, donc elle est perdue et la ligne attend vos 15 minutes complètes. Placez le bail de n8n au-dessus de votre travail le plus lent, puis votre bail de travail au-dessus de celui-ci :
QUEUE_WORKER_LOCK_DURATION=900000
Avec l’ordre inverse, le moissonneur remet le travail à un deuxième worker tandis que le premier l’exécute encore.