Les 59 secondes qui m'ont agacé
Mon bot LinkedIn répondait aux commentaires avec un cron qui tournait toutes les minutes. Un commentaire arrive à 10h00, le cron s'exécute à 10h01, le bot répond à 10h01. Ça marche. Mais si le commentaire arrive à 10h01 et une seconde... il attend jusqu'à 10h02. Un jour, un commentaire important d'un recruteur est resté sans réponse pendant 59 secondes.
Pas dramatique. Mais agaçant. Suffisamment agaçant pour que je me demande : est-ce que le cron est vraiment le bon outil ici ?
Spoiler : pour ce cas précis, non. Mais pour d'autres tâches sur la même machine, il reste imbattable. Voici l'histoire de comment j'ai fini avec les deux approches côté à côté, et pourquoi c'est exactement ce qu'il faut.
Chapitre 1 : quand le cron suffisait largement
Tout a commencé de façon très simple. Deux scripts Node.js à lancer à heure fixe : publier un article sur dev.to à 9h, poster sur LinkedIn à 10h. Classique. La crontab tenait en deux lignes :
0 9 * * * bash ~/work/scripts/devto-cron.sh >> logs/devto-cron.log 2>&1
0 10 * * * /usr/bin/node ~/work/scripts/linkedin-cron.js >> logs/linkedin-cron.log 2>&1
Le script shell charge les variables d'environnement depuis un fichier .env
et appelle le script Node.js. Ce dernier lit devto-schedule.json,
prend le premier article en statut drafted, appelle l'API dev.to,
met à jour le fichier, et s'arrête. Durée d'exécution : quelques secondes.
En cas d'échec, le log contient l'erreur, l'article reste en queue pour le lendemain.
Et ça tournait. Des mois. Sans jamais avoir besoin d'être relancé manuellement. Le cron est génial pour ça : une tâche atomique, sans état, qui n'a pas besoin de savoir ce qui s'est passé avant elle. Un process démarre, fait son travail, s'arrête. Exactement ce pour quoi le cron existe depuis 1975.
Chapitre 2 : le moment où ça a dérappé
Le problème est arrivé avec le système de veille automatisée. Quatre veilles actives (crypto, tech, Epstein, retro), chacune avec sa fréquence propre (de 6h à 7 jours). L'interface d'administration permettait de déclencher une veille manuellement depuis le navigateur. Un job "generate" pouvait être enfilé à tout moment pour reconfigurer une veille via Claude.
J'ai d'abord essayé le cron. Naturellement. Et j'ai commencé à empiler les problèmes :
- Granularité minimale de 1 minute. L'utilisateur clique "Relancer" dans l'interface, attend, ne voit rien se passer pendant potentiellement 59 secondes. L'UX était pénible.
- Pas d'état entre les runs. Pour savoir quand chaque veille avait tourné pour la dernière fois, il fallait lire un fichier JSON à chaque démarrage. Avec quatre veilles qui partagent les mêmes ressources, la coordination devenait fragile.
- Pas de file de jobs. Deux crons qui se déclenchent simultanément
et tentent d'écrire les mêmes fichiers ? Il faut gérer l'overlap avec
flockou équivalent. Ça fonctionne, mais ça demande de l'attention. - Pas de log structuré natif. La sortie standard redirigée vers un fichier devenait vite illisible quand plusieurs veilles écrivaient en parallèle.
Aucun de ces problèmes n'est insurmontable individuellement. Mais empilés, ils m'ont fait comprendre que j'essayais de faire faire au cron quelque chose qui n'était plus son terrain.
Chapitre 3 : le daemon entre en scène
La solution : un daemon Node.js qui tourne en continu. veille-daemon.js
poll jobs.json toutes les 30 secondes pour les jobs on-demand,
et cron-config.json toutes les 60 secondes pour déclencher les veilles
planifiées dont la fréquence est dépassée. Il tient en mémoire le registre des veilles
et l'état de ce qui tourne.
Quand l'utilisateur clique "Relancer" dans l'interface, le PHP écrit un job dans
jobs.json. Le daemon le détecte au prochain poll (au pire 30 secondes),
l'exécute, et met à jour le statut. L'interface peut afficher la progression en temps
réel via un endpoint /veille/status. L'UX est passée de "je clique et
rien ne se passe" à "je clique et ça démarre sous 30 secondes". Nuit et jour.
systemd supervise le daemon avec un fichier .service minimal :
[Unit]
Description=Veille daemon
After=network.target
[Service]
Type=simple
WorkingDirectory=/home/folken/work/cv
ExecStart=/usr/bin/node scripts/veille-daemon.js
Restart=on-failure
RestartSec=10
[Install]
WantedBy=default.target
Ce que ça apporte concrètement : redémarrage automatique en cas de crash,
logs dans journald (journalctl --user -u veille-daemon -f),
démarrage automatique au boot avec systemctl --user enable veille-daemon.
Plus de script de relance manuel, plus de fichier de log à surveiller séparément.
Mais ça a demandé du travail. Quelques jours pour rendre le daemon robuste : gestion des crashes, TTL sur les états bloqués, récupération après redémarrage. Un cron, c'est trois lignes. Un daemon fiable, c'est un vrai morceau de code.
L'entre-deux que j'ai failli oublier : le systemd timer
Entre le cron minimaliste et le daemon complet, il y a une troisième voie que j'ai
sous-estimée pendant longtemps : le systemd timer. C'est un cron amélioré, une tâche
périodique, mais supervisée par systemd. Sur ce projet, crypto-veille.timer
en est un exemple legacy :
# crypto-veille.service
[Unit]
Description=Crypto veille job
After=network.target
[Service]
Type=oneshot
WorkingDirectory=/home/folken/work/cv
ExecStart=/usr/bin/node scripts/crypto-veille.js
# crypto-veille.timer
[Unit]
Description=Crypto veille, toutes les 6h
[Timer]
OnBootSec=5min
OnUnitActiveSec=6h
[Install]
WantedBy=timers.target
Par rapport à un cron, le timer ajoute : des logs natifs dans journald,
la possibilité de déclarer des dépendances (After=network.target),
un délai au boot avec OnBootSec (le cron peut se déclencher trop tôt
si la machine vient de redémarrer), et des expressions de calendrier plus lisibles
que la syntaxe cron classique.
La contrepartie : deux fichiers à gérer au lieu d'une ligne. Pour une tâche simple qui n'a pas besoin de logs structurés et dont la fréquence minimale est 1 minute, le cron reste plus direct. Le timer systemd, c'est le compromis du milieu, quand le cron ne suffit plus mais qu'un daemon serait du sur-engineering.
La carte de synthèse
Après avoir vécu les trois approches sur le même projet, voici comment elles se comparent :
| cron | systemd timer | daemon | |
|---|---|---|---|
| Granularité | 1 min minimum | 1 seconde | libre (polling) |
| Logs | fichier manuel | journald natif | journald natif |
| État entre runs | aucun | aucun | en mémoire |
| Réaction on-demand | non | non | oui |
| Supervision crash | non | oui | oui |
| Complexité config | très faible | moyenne (2 fichiers) | élevée (code à écrire) |
| Cas idéal | tâche atomique périodique | tâche périodique + logs/deps | file de jobs, état, on-demand |
La règle des trois questions
Après cette expérience, j'ai dégagé trois questions qui suffisent à trancher :
1. La tâche doit-elle réagir à un événement en moins d'une minute ?
Si oui : daemon. Le cron ne peut pas faire mieux qu'une minute, et le timer systemd
non plus (il exécute des tâches planifiées, pas réactives).
2. A-t-elle besoin d'état entre les runs ?
Si oui : daemon. Lire et écrire un fichier à chaque run fonctionne jusqu'à un certain
point, mais dès qu'il y a plusieurs workers ou des décisions à prendre sur l'historique
récent, un process continu avec état en mémoire est plus propre.
3. Est-ce que la supervision et les logs structurés comptent ?
Si oui mais que les deux premières réponses sont non : systemd timer.
Il donne les bénéfices de systemd (journald, redémarrage, dépendances) sans
la complexité d'écrire un event loop.
Si les trois réponses sont non : cron. Trois lignes dans la crontab, une redirection vers un fichier log, zéro infrastructure supplémentaire. Ne pas sur-engineer ce qui n'en a pas besoin.
Épilogue : les deux coexistent, et c'est bien comme ça
Aujourd'hui, sur la même machine, le cron continue de publier mes articles à heure fixe. Il n'a jamais eu besoin d'être relancé manuellement. Le daemon de veille, lui, a nécessité quelques jours de travail pour être robuste, mais il répond en 30 secondes là où le cron aurait mis jusqu'à une minute.
Ce n'est pas cron OU daemon. C'est reconnaître lequel des deux correspond au problème posé. La publication à heure fixe n'a pas besoin de réagir en 30 secondes. La veille interactive ne peut pas se permettre d'attendre une minute entre deux checks.
Et les 59 secondes de retard sur LinkedIn ? Le bot tourne maintenant en daemon. Le recruteur n'a plus à attendre.