Skip to content

BDD-163 — Corriger la perte de suivi des exécutions de workflow sous forte charge

1. User Story

En tant qu'administrateur, je veux que le statut d'une exécution de workflow reste consultable même en cas de forte concurrence, afin de ne pas voir des exécutions pourtant terminées avec succès apparaître comme introuvables ou bloquées indéfiniment.

2. Contexte

Découvert lors du test de charge BDD-162 : à partir d'environ 35-40 exécutions de workflow simultanées, GET /workflows/:workflowUuid/runs/:runUuid/status renvoie une erreur 404 pour une partie des exécutions — alors que celles-ci se terminent normalement en base de données. Le nombre d'exécutions concernées correspond exactement à N − 32 (N étant le nombre d'exécutions simultanées), confirmé par les logs serveur (WorkflowNotFoundException) sur plusieurs paliers de charge (40, 50).

3. Critères d'acceptance

#CritèreStatut
1Le cache de suivi en mémoire (WorkflowRunLiveProgressService) n'évince jamais une exécution dont le statut est encore « en cours » (running)
2Quand le statut d'une exécution n'est plus disponible en mémoire, l'API retombe sur la table workflow_runs pour renvoyer le statut réel au lieu d'une erreur 404
3Un test automatisé vérifie qu'une exécution « en cours » n'est jamais évincée du cache même quand la limite est atteinte
4Un test automatisé vérifie que l'API retourne le bon statut quand l'entrée mémoire est absente mais que l'exécution existe en base
5Rejouer les scripts de test de charge (back/test/load/) à VUS=40 et VUS=50 confirme un taux d'exécutions terminées de 100 %
6Le filet de sécurité contre une entrée running orpheline (TTL) n'évince pas une exécution encore réellement en cours sous forte charge

4. Périmètre fonctionnel

Backend

FichierModification
back/src/api/workflows/workflow-run-live-progress.service.tsenforceMaxEntries() n'évince plus que des entrées déjà terminées (completed/failed) ; si toutes les entrées sont running, aucune éviction n'a lieu (avertissement loggé à la place).
back/src/api/workflows/staging/workflow-run-staging-execution.service.tsgetLiveRunStatus() retombe sur WorkflowRunHistoryService.getRunDetail() (déjà injecté) quand l'entrée mémoire est absente, et reconstruit un WorkflowRunLiveStatusDto à partir de l'historique persisté au lieu de renvoyer 404.
back/src/api/workflows/workflow-run-live-progress.service.tsLIVE_RUN_TTL_RUNNING_MS porté de 15 à 60 minutes : découvert en testant au-delà de VUS=50 (BDD-162), une exécution encore en cours pouvait être purgée par ce filet de sécurité temporel avant même d'atteindre la limite de capacité (32), reproduisant le même symptôme par un autre chemin.

Aucun script SQL n'est nécessaire : le correctif ne modifie ni le schéma Prisma ni les données, il change uniquement la logique applicative (politique d'éviction du cache + repli sur une lecture déjà possible en base).

5. Comportement détaillé

Éviction du cache de suivi (P0)

Avant ce correctif, enforceMaxEntries() évinçait systématiquement l'entrée la plus ancienne du cache dès que celui-ci atteignait MAX_LIVE_RUN_ENTRIES (32), y compris une exécution encore en cours. La correction filtre d'abord les entrées déjà terminées ; seule une entrée completed/failed peut être évincée. Si les 32 entrées sont toutes running (charge réellement supérieure à la capacité du cache), aucune éviction n'a lieu et un avertissement est loggé — le cache dépasse alors temporairement sa capacité nominale plutôt que de perdre le suivi d'une exécution active.

Repli sur l'historique en base (P0)

Avant ce correctif, une entrée absente du cache mémoire (évincée, ou expirée après le TTL de 3 minutes appliqué aux exécutions terminées) provoquait systématiquement une WorkflowNotFoundException (404), même si l'exécution existait et s'était terminée normalement en base. La correction appelle WorkflowRunHistoryService.getRunDetail() — déjà utilisé par l'écran d'historique — pour reconstruire un statut completed fidèle à partir de workflow_runs/workflow_run_steps. Cette méthode lève déjà WorkflowNotFoundException si l'exécution n'existe réellement pas, ce qui préserve le comportement existant pour les identifiants invalides.

TTL de sécurité trop court (P0, découvert en poussant au-delà de VUS=50)

En rejouant les scripts de charge à VUS=100 pour valider les deux correctifs précédents, le même symptôme (404 sur une exécution encore active) est réapparu par un chemin différent : purgeExpired() supprime une entrée running après LIVE_RUN_TTL_RUNNING_MS (15 minutes à l'origine), qu'elle soit orpheline ou simplement lente. Vérifié en base : le schéma de staging de l'exécution concernée existait toujours et le process ne consommait aucun CPU au moment de la purge — l'exécution était réellement toujours en cours, pas plantée. Sous forte charge, dépasser 15 minutes est plausible et ne doit pas être traité comme une anomalie. Le délai est porté à 60 minutes, qui reste un filet de sécurité contre une entrée réellement orpheline (crash empêchant complete()/fail()) sans purger prématurément une exécution seulement lente.

6. Tests

FichierCas couverts
back/src/api/workflows/workflow-run-live-progress.service.spec.tsUne entrée running n'est jamais évincée même au-delà de la capacité du cache ; une entrée terminée est évincée en priorité sur une entrée en cours.
back/src/api/workflows/staging/workflow-run-staging-execution.service.spec.tsgetLiveRunStatus retombe sur l'historique en base quand l'entrée mémoire est absente ; propage bien une 404 si l'exécution est réellement introuvable.
back/src/api/workflows/workflow-run-live-progress.service.spec.tsUne entrée running survit à 45 minutes (sous le nouveau seuil) ; une entrée orpheline est bien purgée au-delà de 60 minutes.

7. Vérification en conditions de charge

Ticket lié : BDD-162 (méthodologie et scripts de test de charge dans back/test/load/).

Rejoué sur BDD-162/workflow-load-test avec les deux commits de ce ticket fusionnés localement :

VUSAvant correctifAprès correctif
4080 %100 %
5064 %100 %
100non testé (seuil déjà dépassé)100 % (suivi) — voir note ci-dessous

Commande type :

bash
docker run --rm -i -v "$(pwd)/back/test/load:/scripts" grafana/k6 run \
  --env BASE_URL=http://host.docker.internal:3000 \
  --env APP_ACCESS_TOKEN_SECRET=<valeur de back/.env> \
  --env WORKFLOW_UUID=<uuid-workflow-de-test> \
  --env ENTERPRISE_ID=<enterprise_id-local> \
  --env VUS=40 \
  /scripts/scenarios/same-user.k6.js

Note : à VUS=100, le suivi est désormais fiable à 100 %, mais une proportion croissante d'exécutions échoue réellement au niveau métier (erreurs S3DownloadBuffer intermittentes en téléchargeant le fichier source sous forte concurrence). Ceci est indépendant de ce ticket — c'est une vraie limite de capacité, pas un problème de suivi — et fait l'objet d'un constat séparé dans le rapport BDD-162.