Image de couverture : Symfony Lock Component : sécurisez vos tâches asynchrones sans Redis ni sur-ingénierie
tech

Symfony Lock Component : sécurisez vos tâches asynchrones sans Redis ni sur-ingénierie

25 May 2026
5 min de lecture
5 vues
Sébastien Muler

Symfony Lock Component : sécurisez vos tâches asynchrones sans Redis ni sur-ingénierie

Vous avez un cron de facturation qui tourne toutes les cinq minutes. Vous passez à deux serveurs applicatifs. Résultat : les factures sont débitées deux fois et le support croule sous les tickets. Un collègue propose aussitôt Redis, un script Lua, un nouveau service à maintenir.

Stop.

Avant d'ajouter de la complexité infra, posez-vous la bonne question : avez-vous déjà une base de données ? Si oui, vous avez tout ce qu'il vous faut. Le composant symfony/lock résout ce problème en une cinquantaine de lignes de PHP, sans aucune nouvelle brique technique.


Pourquoi les verrous distribués existent

Le problème est aussi vieux que les systèmes concurrents : deux processus veulent exécuter une tâche. Cette tâche ne doit se produire qu'une seule fois. Sans coordination, les deux processus l'exécutent, et les effets de bord s'accumulent.

Un exemple concret : un job d'envoi de digest hebdomadaire tournant sur trois workers derrière un load balancer. Le job est « idempotent sur le papier ». En pratique, l'appel au prestataire d'emailing ne l'est pas, et les utilisateurs reçoivent trois fois le même email le lundi matin.

La solution classique consiste à ajouter Redis + un script Lua pour gérer l'atomicité. C'est légitime dans certains contextes, mais c'est souvent du canon pour tuer une mouche.


Ce que fait vraiment symfony/lock

Le composant symfony/lock implémente le pattern de verrou distribué avec une API unifiée, quel que soit le backend choisi. Il supporte nativement :

  • PostgreSQL via les advisory locks (PdoStore)
  • MySQL via GET_LOCK (PdoStore)
  • Redis / Redlock si vous l'avez déjà
  • Memcached, MongoDB, ZooKeeper
  • Un backend fichier pour les setups mono-serveur

Concrètement : si vous avez déjà Postgres ou MySQL dans votre stack — ce qui est le cas de la grande majorité des projets PHP — vous n'avez aucune nouvelle infrastructure à provisionner.

Installation

composer require symfony/lock

C'est tout. Le composant fonctionne dans un projet Symfony, dans un projet Laravel, ou dans un script PHP vanilla.


Mise en œuvre : le cas du cron de facturation

Voici comment protéger un job critique contre les exécutions concurrentes.

1. Configurer le store

Avec PostgreSQL ou MySQL, on utilise PdoStore en lui passant simplement le DSN de la base existante :

use Symfony\Component\Lock\Store\PdoStore;
use Symfony\Component\Lock\LockFactory;

$store = new PdoStore('pgsql:host=localhost;dbname=myapp', [
    'db_username' => 'user',
    'db_password' => 'secret',
]);

$factory = new LockFactory($store);

Dans un projet Symfony avec le bundle configuré, l'injection de LockFactory se fait directement via le conteneur.

2. Acquérir et libérer le verrou

$lock = $factory->createLock('billing-cron', ttl: 300);

if (!$lock->acquire()) {
    // Un autre processus tient déjà le verrou, on sort proprement
    return;
}

try {
    // Votre logique métier ici
    $this->processPendingInvoices();
} finally {
    $lock->release();
}

Le paramètre ttl (en secondes) est crucial : il garantit que le verrou sera libéré même si le processus plante, évitant ainsi le deadlock permanent.

3. Auto-refresh pour les tâches longues

Si votre job peut dépasser le TTL, le composant propose un mécanisme de renouvellement automatique :

$lock = $factory->createLock('billing-cron', ttl: 300, autoRelease: true);

if ($lock->acquire()) {
    // Le verrou se renouvelle automatiquement tant que le processus tourne
    $lock->refresh(); // à appeler périodiquement si besoin
}

Cette approche est bien plus fiable qu'un flag en base de données géré manuellement — pattern que l'on voit encore trop souvent dans des codebases PHP legacy.


Ce que vous évitez en faisant ça correctement

Voici les anti-patterns courants que symfony/lock remplace avantageusement :

Le flag is_running en base : une colonne booléenne mise à true en début de job, remise à false à la fin. Le problème : si le processus plante, le flag reste à true et le job ne tourne plus jamais. Il faut alors une intervention manuelle ou un mécanisme de timeout ad hoc.

Le fichier de lock maison : un fichier /tmp/billing.lock vérifié en début de script. Ça fonctionne sur un seul serveur, mais pas en environnement distribué. Et la gestion des cas d'erreur est rarement robuste.

Redis « pour les locks » : légitime si Redis est déjà dans la stack pour d'autres raisons. Mais l'ajouter uniquement pour les verrous distribués, alors qu'on a déjà Postgres, c'est de la complexité infra sans bénéfice réel.


Conclusion : choisir le bon outil, pas le plus impressionnant

Le composant symfony/lock illustre un principe fondamental d'architecture : résoudre le bon problème avec le minimum de complexité accidentelle. Avant d'ajouter une brique technique, demandez-vous si l'écosystème existant ne propose pas déjà une solution fiable.

Dans la plupart des projets PHP avec une base de données relationnelle, la réponse est oui. Les advisory locks de PostgreSQL et GET_LOCK de MySQL sont des primitives robustes, éprouvées, et symfony/lock vous en offre une abstraction propre avec une API cohérente.

Votre cron de facturation mérite un verrou distribué. Il ne mérite pas nécessairement une nouvelle infrastructure.

💡 Cet article s'inspire du post de Gabriel Anhaia sur DEV.to : Symfony Lock Component: Distributed Locks in 50 Lines (No Redis Required).

Partager cet article