Image de couverture : Jobs idempotents : concevoir des retries sans double effet
PHP & Frameworks

Jobs idempotents : concevoir des retries sans double effet

4 novembre 2026
6 min de lecture
4 vues
Sébastien Muler

Un worker plante au pire moment, et la tâche repart de zéro

Un paiement vient d'être débité, le worker s'arrête avant d'avoir noté que tout s'est bien passé. Le broker, qui n'a reçu aucun accusé de réception, redistribue la tâche. Résultat : le client est facturé deux fois. Ce scénario n'a rien d'exotique : il découle directement du fonctionnement des files d'attente, et c'est à vous de le gérer dans votre code.

Cet article s'appuie sur Idempotent Jobs & Exactly-Once Effects: Designing Safe Retries, publié sur DEV Community, qui ouvre une série consacrée aux files fiables dans les SaaS Laravel et Node. Nous en reprenons l'idée centrale pour proposer une démarche d'audit applicable à vos propres jobs.

Pourquoi vos jobs vont forcément s'exécuter plusieurs fois

La plupart des files (Redis, SQS, base de données) offrent une livraison au moins une fois. Le broker préfère vous livrer un message en double plutôt que d'en perdre un. Une même tâche peut donc être rejouée après :

  • un crash du worker en cours de traitement ;
  • un déploiement qui coupe les processus ;
  • l'expiration d'un verrou ou d'un délai de visibilité ;
  • une redélivrance par le broker.

La livraison « exactement une fois » est, en pratique, un mirage au niveau du transport. Ce que vous pouvez garantir, c'est un effet exactement une fois : le job peut tourner dix fois, le résultat observable dans votre système ne se produit qu'une seule fois. C'est la définition de l'idempotence appliquée aux effets de bord.

Les trois effets les plus dangereux à rejouer

Tous les jobs ne se valent pas face au retry. Un recalcul de statistiques se rejoue sans dommage. En revanche, trois familles d'opérations posent problème :

  • L'argent : débit d'une carte, création de facture, remboursement.
  • Les communications : e-mail de bienvenue, notification, SMS.
  • Les compteurs : décrémentation d'un stock, incrémentation d'un solde de crédits.

Le point commun : l'opération modifie un état du monde que vous ne pouvez pas annuler d'un simple rejeu. Commencez votre audit par ces jobs.

Les mécanismes pour obtenir un effet unique

La clé d'idempotence

Chaque opération sensible reçoit un identifiant stable, dérivé de la donnée métier et non du job lui-même (par exemple l'identifiant de la commande plus l'action visée). Cet identifiant est transmis aux services externes qui le supportent, comme les API de paiement qui acceptent une clé d'idempotence : un second appel avec la même clé renvoie le résultat du premier au lieu de refaire l'opération.

Le registre d'exécution en base

Pour les effets internes, on enregistre l'opération dans une table avec une contrainte d'unicité sur la clé. Le principe, en pseudo-code :

public function handle(): void
{
    $key = sprintf('welcome-email:%d', $this->userId);

    try {
        DB::transaction(function () use ($key) {
            ProcessedJob::create(['key' => $key]); // échoue si la clé existe déjà
            Mail::to($this->user())->send(new WelcomeMail());
        });
    } catch (UniqueConstraintViolationException) {
        return; // déjà traité : on ignore le rejeu
    }
}

La contrainte d'unicité fait le travail à votre place, y compris quand deux workers traitent la même tâche en parallèle. Attention toutefois à l'ordre : si l'envoi externe réussit puis que la transaction échoue, le rejeu enverra à nouveau. Pour les effets externes, la clé transmise au fournisseur reste la meilleure protection.

Les opérations naturellement idempotentes

Quand c'est possible, préférez des opérations qui produisent le même état quel que soit le nombre d'exécutions. Écrire « le statut vaut payé » se rejoue sans risque, alors qu'incrémenter un compteur ne le permet pas. De même, un upsert vaut mieux qu'un insert aveugle.

Le verrou, un complément et non une solution

Un verrou empêche deux exécutions simultanées, mais il expire, et il ne protège pas d'un rejeu séquentiel après un crash. Considérez-le comme une optimisation qui réduit les doublons, jamais comme la garantie.

Comment auditer vos files existantes

Une démarche simple pour un projet Laravel ou Node déjà en production :

  • Listez les jobs qui ont un effet de bord irréversible (paiement, e-mail, stock).
  • Pour chacun, posez la question : que se passe-t-il s'il s'exécute deux fois, ou s'il est interrompu à mi-chemin ?
  • Identifiez la clé métier qui permettrait de reconnaître un doublon.
  • Ajoutez la contrainte d'unicité ou la clé d'idempotence côté fournisseur.
  • Écrivez un test qui exécute le job deux fois de suite et vérifie que l'effet n'a eu lieu qu'une fois.

Ce dernier test est le plus rentable : il transforme une hypothèse implicite en garantie vérifiée à chaque déploiement.

Conclusion

Les retries sont inévitables, et c'est une bonne chose : ils rendent votre système résilient. À condition que chaque job accepte d'être rejoué sans conséquence. En identifiant les effets sensibles, en leur donnant une clé stable et en laissant la base ou le fournisseur arbitrer les doublons, vous évitez les corruptions de données et les doubles facturations. Pour approfondir, la suite de la série sur les files fiables abordera la gestion des messages en échec et le rejeu sécurisé, après l'article d'origine.

Ce que ça change pour vous

Quand un système informatique plante au milieu d'une tâche, il la recommence automatiquement. Sans précaution, un client peut alors être facturé deux fois ou recevoir plusieurs fois le même message. Ces erreurs abîment la confiance et génèrent des remboursements et du temps perdu en service client. Prévoir ce cas dès la conception coûte peu, alors que réparer des doublons après coup coûte cher. Demandez à votre prestataire comment il protège vos paiements et vos envois automatiques contre ce risque.

Partager cet article

Et votre site, il vaut quoi ?

Vitesse, accessibilité, référencement technique. Le rapport est écrit pour un dirigeant, pas pour un développeur. Gratuit, sans inscription.

Tester mon site