Supervision des crons graphile¶
Vocabulaire¶
Quatre objets distincts, souvent confondus — y compris dans nos propres commentaires de revue.
| Mot | Ce que c'est | Où il vit |
|---|---|---|
| task | le code, identifié par task_identifier (sync-lw-wires) |
le taskList du worker |
| cron | une entrée de planning qui crée des jobs pour une task | cronItems dans le code, suivi dans _private_known_crontabs |
| job | une unité de travail en file : une task, un payload, une heure, un compteur de tentatives | graphile_worker.jobs — supprimée au succès |
| run | une tentative d'exécution d'un job | nulle part : graphile n'en garde rien |
La chaîne : un cron crée un job, qui exécute une task, en une ou plusieurs runs.
Conséquence pour l'UI : « abandonné » est un état de job (la machine à retenter a renoncé), « échec » est l'issue d'un run. Les deux ne se comparent pas ligne à ligne — un job abandonné a produit 25 runs en échec.
Piège : l'identifiant d'un cron n'est pas celui de sa task. _private_known_crontabs.identifier
vaut sync-lw-wires-task là où jobs.task_identifier vaut sync-lw-wires. Le catalogue du
dashboard s'appuie sur cette différence — les « corriger » pour qu'ils coïncident casse silencieusement
l'affichage du dernier déclenchement.
La décision¶
Le dashboard back-office (/administration/crons, /administration/queues, /administration/jobs)
ne lit que ce que graphile expose. Aucune table maison, aucun code posé sur les tâches de
production.
Une couche d'audit a été écrite puis retirée avant le merge (PR #5953). Le pourquoi et la façon de la reprendre sont en bas de page.
Ce que graphile donne, et ce qu'il ne donne pas¶
| Besoin | Source | Limite |
|---|---|---|
| Catalogue des crons | cron-registry.ts (le code) |
— |
| Catalogue des queues | queue-registry.ts (le code) |
pas de crontab : une task queue n'a pas de lastExecutionAt |
| Dernier déclenchement | graphile_worker._private_known_crontabs.last_execution |
dit que la planification a tourné, pas que la tâche a réussi — CRON only |
| File en cours | vue graphile_worker.jobs |
un job réussi est supprimé : la file ne contient que l'attente, l'en-cours et le cassé |
| Message d'erreur | jobs.last_error |
le message seul — la stack part au logger (worker.js:255), jamais en base |
| Relancer / abandonner | reschedule_jobs, permanently_fail_jobs |
ignorent silencieusement un job verrouillé : 0 ligne renvoyée |
Conséquence directe : un cron sain n'apparaît nulle part. Le diagnostic opérationnel se lit en
croisant « déclenché récemment » et « rien de coincé dans la file ». Même lecture pour une queue :
une task saine a une file vide. Le payload n'est pas sur la liste CRON ; GET
/administration/queues/:task/jobs le joint depuis _private_jobs parce qu'un job queue sans
projectId est illisible.
Les pièges de graphile-worker (0.16.6)¶
makeWorkerUtilsmigre le schéma à l'initialisation (dist/lib.js:303), sans option pour l'éviter (issue #475, fermée). Un simple bouton « relancer » aurait fait migrer la base depuis le process web. On appelle les fonctions SQL directement.- Aucun statut stocké : il se déduit du verrou et du compteur de tentatives. Cette dérivation
doit vivre une seule fois — ici un
CASEen SQL, sur lequel la liste filtre via une table dérivée. Deux écritures de la même règle finissent par diverger, et le filtre contredit le libellé. - La vue
jobsn'expose paspayload: seule colonne lue dans_private_jobs, à revérifier à chaque montée de version. - Un job n'est pas une exécution : la ligne survit à ses 25 tentatives, seul le compteur bouge.
- La planification est figée au boot : changer un horaire demande de redémarrer le runner
(issue #318), et
backfillPeriod: 0signifie qu'un cron manqué n'est jamais rattrapé. - Les verrous d'un worker mort sont libérés en 8 à 10 minutes (
dist/config.js:28), et seulement par un runner en marche.
Si on reprend l'historique des runs (V2)¶
Le code existe, relu et testé, dans le commit 6b489955e :
git checkout 6b489955e -- projects/api/migration/flyway/V202608141500__create-audit-graphile-task-run.sql
git show 6b489955e:projects/api/src/__new/lib/graphile-worker/create-task-list-with-run-audit.ts
Ce qu'il apporte : statut réel par exécution, durée, erreur complète ({ name, message, stack,
causes }), et un statut interrupted pour les process tués.
Quatre contraintes découvertes en route, à ne pas réapprendre :
- L'écriture d'audit ne doit jamais faire échouer une tâche déjà exécutée. Elle tourne dans la promesse de la tâche : si l'erreur remonte, graphile marque le job en échec et rejoue un cron dont les effets sont commités — sur un cron de paiement, c'est payer deux fois. Avaler l'échec, après quelques tentatives.
- Un run laissé ouvert doit être balayé, mais pas au seul démarrage du worker : un process qui
crashe et redémarre voit ses propres jobs encore verrouillés. Balayage périodique, par corrélation
avec le verrou graphile, jamais par seuil de durée (six minutes sont normales pour
refresh-leaderboard). interruptedveut dire « issue inconnue », pas « n'a pas tourné » : une panne d'écriture au moment de clôturer produit le même état qu'un process tué. L'UI ne doit pas laisser croire à l'opérateur que la tâche n'a pas eu lieu.- Le wrapper va sur le
taskListdu runner, pas danscreateCronTask: 3 tâches sur 27 l'utilisent, les 24 autres construisent leurTaskà la main.