AGENT SKILL

volontariapp-async-remediation

Playbook opérationnel pas-à-pas pour diagnostiquer, débloquer et réconcilier les pannes asynchrones (Poison pills DLQ, Redis Streams XCLAIM/XACK, réconciliation SQL outbox).

使用场景后端与接口
License 许可
MIT
所属仓库
Volontariapp/meta
最近更新
2026-11-10
Stars
★ 0
Forks
0

01 前言 Front Matter

name
volontariapp-async-remediation
tags
[ "async", "remediation", "dlq", "redis-streams", "xclaim", "xack", "outbox", "playbook" ]
type
Playbook
paths
[ "post-processors-runner/**", "workers-runners/**", "npm-packages/packages/post-processors/**" ]
title
Remédiation et Dépilement des Files Asynchrones (DLQ & Streams)
status
stable
sources
[ { "id": "retry", "kind": "local_file", "title": "RetryHelper (DLQ <stream>-dlq)", "resource": "npm-packages/packages/post-processors/src/core/helpers/retry.helper.ts" }, { "id": "outbox-status", "kind": "local_file", "title": "OutboxStatus", "resource": "npm-packages/packages/database/src/outbox/types/outbox.status.ts" }, { "id": "panne", "kind": "local_file", "title": "Scénarios de panne, problèmes P2 et P4", "resource": "docs/stockage-fichiers/11-scenarios-de-panne.md" } ]
verified
[ { "at": "2026-11-04T21:34:45Z", "by": "claude-code/agent", "digest": "33b9ef4b5b3df65a" }, { "at": "2026-11-04T21:36:01Z", "by": "claude-code/agent", "digest": "036777c37dd4f448" }, { "at": "2026-11-07T23:14:09Z", "by": "claude-code/agent", "digest": "2360205de2f7abcd" }, { "at": "2026-11-07T23:14:26Z", "by": "claude-code/agent", "digest": "2360205de2f7abcd" }, { "at": "2026-11-08T12:02:22Z", "by": "claude-code/agent", "digest": "9c93894442a0fd6f" }, { "at": "2026-11-08T12:07:09Z", "by": "claude-code/agent", "digest": "2a04ea25b2d069e3" } ]
generated
{ "at": "2026-10-12T11:58:00Z", "by": "claude-code/agent" }
mesh_keys
[ "dlq-remediation", "xclaim-recovery", "stream-remediation" ]
description
Playbook opérationnel pas-à-pas pour diagnostiquer, débloquer et réconcilier les pannes asynchrones (Poison pills DLQ, Redis Streams XCLAIM/XACK, réconciliation SQL outbox).

02 资源文件 3

03 SKILL.md 正文

Remédiation et Dépilement des Files Asynchrones (DLQ & Streams)

Quand utiliser ce playbook

Utiliser ce playbook pour intervenir en production ou en environnement de test en cas d'incident sur un flux asynchrone :

  • Messages arrivés en fin de tentatives et routés vers la Dead Letter Queue (<stream>-dlq)^retry.
  • Blocage d'un groupe de consommateurs Redis Streams (consommateur crashé, PEL saturée).
  • Erreur récurrente sur un Job BullMQ sans reprise automatique.
  • Désynchronisation ou blocage persistant de statut dans jobs_outbox, event_queue ou job_audit^outbox-status.

(Pour une phase d'audit passif préalable sans action corrective, se référer à la skill d'investigation volontariapp-trace-async-flow).


Règles d'or d'intervention

  1. Aucune suppression aveugle : Ne jamais exécuter un XDEL ou purger une DLQ sans avoir copié et sauvegardé le payload JSON incriminé.
  2. Idempotence des consommateurs : Tout rejeu de message (replay) suppose que le consommateur cible supporte d'être appelé deux fois pour la même action (gestion du « déjà traité »).
  3. Consistance transactionnelle bilatérale : Débloquer un message dans Redis nécessite systématiquement de vérifier et réconcilier l'état de la ligne correspondante dans la base PostgreSQL du microservice émetteur (event_queue ou jobs_outbox)^panne.

Procédure pas-à-pas

Étape 1 : Inspection de la Poison Pill dans la DLQ

Lorsqu'un post-processor échoue après maxRetries (par défaut 5 tentatives), RetryHelper bascule le message dans <stream>-dlq^retry.

Seules les erreurs métier suivent ce chemin. Une erreur d'infrastructure (connexion base ou Redis refusée ou perdue, timeout, détectée par isInfrastructureError de @volontariapp/errors) ne compte aucune tentative et n'envoie rien en DLQ : le message reste dans la PEL et le consumer le relit claimIntervalMs plus tard (un message absent de la DLQ pendant une panne de base est donc normal). Chaque occurrence est loguée en warn (Infrastructure error, message kept pending) avec le stream et l'id du message. Le circuit breaker, lui, compte toujours l'échec.

  1. Se connecter à l'instance Redis :
    redis-cli
    
  2. Inspecter les derniers messages en DLQ :
    XRANGE <stream>-dlq - + COUNT 10
    
  3. Chaque entrée contient :
    • messageId : ID d'origine du message dans le flux initial.
    • error : Message et stacktrace de l'exception ayant provoqué l'échec.
    • payload : Contenu JSON complet de l'événement d'origine.
    • timestamp : Horodatage du basculement en DLQ.
  4. Sauvegarder localement le payload et analyser la racine de l'échec (ex. entité introuvable, schéma désynchronisé, contrainte de clé étrangère violée).

Étape 2 : Déblocage des messages orphelins (Pending Entries List)

Si un worker ou post-processor a crashé pendant le traitement, le message reste bloqué dans la Pending Entries List (PEL) sans être ni acquitté ni retenté.

Le claim (claimPendingMessages) ignore les messages du même consumer : un message resté en PEL chez un consumer vivant n'est relu que lorsque readPending repasse à true (démarrage, claim, file de retry, ou relecture planifiée après une erreur d'infrastructure). Piège connu : le verrou d'idempotence d'un message (SET NX, TTL idempotencyTtlSeconds, 24 h) non libéré parce que Redis était indisponible fait traiter la relecture comme « déjà traité » : le message est alors acquitté sans effet. Dans ce cas, supprimer la clé de verrou (RedisStreamHelper.getIdempotencyKey) avant de redémarrer le pod, car le démarrage relit la PEL.

  1. Vérifier les messages en souffrance pour le groupe consommateur :
    XPENDING <stream> <nom-du-groupe>
    
  2. Lister les identifiants et les consommateurs assignés :
    XPENDING <stream> <nom-du-groupe> - + 10
    
  3. Si un consommateur est inactif depuis plus de 5 minutes (300 000 ms), réclamer le message au profit d'un consommateur sain via XCLAIM :
    XCLAIM <stream> <nom-du-groupe> <nouveau-consommateur> 300000 <messageId>
    
  4. Si le message a été traité avec succès de manière externe, l'acquitter manuellement :
    XACK <stream> <nom-du-groupe> <messageId>
    

Étape 3 : Correction de la cause racine

Avant toute réinjection :

  1. Si bug de logique applicative : Livrer le correctif dans le microservice ou le post-processor (post-processors-runner), redémarrer le pod.
  2. Si anomalie de données en base : Corriger la donnée orpheline ou invalide directement dans la base PostgreSQL du service concerné.

Étape 4 : Réinjection sécurisée (Replay) depuis la DLQ

Une fois le correctif en place :

  1. Rejouer l'événement dans le flux nominal :
    XADD <stream> * payload '<json-payload-corrigé>'
    
  2. Confirmer la prise en charge et le traitement réussi par le post-processor via les logs applicatifs.
  3. Purger le message résolu de la DLQ pour maintenir la file propre :
    XDEL <stream>-dlq <id-du-message-dlq>
    
  4. Nettoyer les clés de métadonnées de retry résiduelles créées par RetryHelper^retry :
    DEL retry:post-processor:<groupe>:<messageId>
    DEL retry:post-processor:<groupe>:<messageId>:attempt
    DEL retry:post-processor:<groupe>:<messageId>:lastError
    ZREM retry-queue:post-processor:<groupe> <messageId>
    

Étape 5 : Réconciliation transactionnelle SQL

Pour garantir la cohérence des compteurs et des sagas métier :

  1. Identifier la ligne dans la base du microservice émetteur (event_queue ou jobs_outbox)^outbox-status :
    SELECT id, event_name, status, retry_count, last_error
    FROM event_queue
    WHERE id = '<id-evenement>';
    
  2. Si le message a été réinjecté et traité, synchroniser le statut :
    UPDATE event_queue
    SET status = 'COMPLETED', updated_at = NOW()
    WHERE id = '<id-evenement>' AND status = 'FAILED';
    
  3. Vérifier la table job_audit pour confirmer la clôture propre du cycle d'exécution.

Étape 6 : Enregistrement du piège (Boucle de progrès)

Si la panne a révélé une vulnérabilité d'architecture ou un comportement inattendu, consigner immédiatement la leçon apprise :

python3 .agents/skills/volontariapp-skill-evolution/scripts/evolve.py learn \
  --skill volontariapp-async-remediation \
  --kind pitfall \
  --rule "Description de la cause racine et de la règle pour éviter la récidive."

Checklist de remédiation

  • [ ] Le payload du message en échec a été exporté et analysé depuis <stream>-dlq^retry.
  • [ ] La PEL Redis a été auditée (XPENDING) et les messages orphelins réclamés (XCLAIM) ou acquittés (XACK).
  • [ ] La cause racine (applicative ou modèle de données) a été corrigée et vérifiée.
  • [ ] Le message a été réinjecté (XADD) et traité avec succès par les consommateurs.
  • [ ] Le message traité a été supprimé de la DLQ (XDEL) et les clés de retry nettoyées.
  • [ ] Les statuts de l'outbox relationnelle (event_queue / jobs_outbox) ont été réconciliés (COMPLETED)^outbox-status.
  • [ ] La leçon a été consignée via evolve.py learn si un piège structurel a été mis en évidence.

Références

04 同仓库更多 Skills

Volontariapp
Volontariapp/meta

volontariapp-user-account-deletion

Suppression de compte en deux temps (PENDING_DELETION): requestDeletionWithUserDeleted, finalizeDeletion, fan-out de user.deleted vers les domaines et gather ws. A utiliser pour tout changement de domain-user touchant la suppression, le statut ou le cycle de vie d'un user.

★ 0
Volontariapp
Volontariapp/meta

volontariapp-proto-contract-evolution

Modifier un contrat Protobuf de proto-registry (champ, message, enum, RPC) sans casser la compatibilité wire, puis suivre la cascade proto-registry, PR automatique dans npm-packages, publication, consommateurs. À utiliser avant toute édition d'un .proto.

律师与法务
★ 0
Volontariapp
Volontariapp/meta

volontariapp-meta-scripts

Utiliser et faire evoluer la CLI Node de meta (setup, doctor, migrate, certs, repos, bump) et le manifeste repos.json, source unique des repos et unites du workspace.

Skill与智能体开发
★ 0
Volontariapp
Volontariapp/meta

volontariapp-implement-async-event-flow

Guide pas-à-pas pour concevoir et implémenter un flux asynchrone complet (messaging -> outbox -> post-processors -> ws-service -> client).

前端开发
★ 0
Volontariapp
Volontariapp/meta

research

Investigate a question against high-trust primary sources and capture the findings as a Markdown file in the repo. Use when the user wants a topic researched, docs or API facts gathered, or reading legwork delegated to a background agent.

资料检索与研究
★ 0
Volontariapp
Volontariapp/meta

prototype

Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like.

设计师
★ 0