Passer au contenu
DedicatedPHP Contact

Verrous distribués avec Redis en PHP : quand ils protègent et quelles sont leurs limites

Découvrez quand un verrou distribué avec Redis est utile en PHP, comment l’acquérir et le libérer en toute sécurité, et quelles garanties la base de données doit également apporter.

Schéma conceptuel de plusieurs workers PHP coordonnés par un verrou temporaire dans Redis et une base de données

Lorsque plusieurs workers PHP peuvent exécuter simultanément une tâche sur la même ressource, un verrou peut réduire le travail effectué en double. On envisage souvent cette solution pour générer un rapport, traiter une commande ou mettre à jour une ressource partagée. Mais utiliser Redis ne rend pas à lui seul une opération cohérente : le processus peut être suspendu, le verrou peut expirer et un autre worker peut commencer avant que le premier ait terminé.

La question importante n’est pas seulement de savoir comment créer une clé, mais quelle propriété doit être protégée et quel système peut la faire respecter. Dans de nombreux cas, une contrainte d’unicité ou une transaction de base de données offre une garantie plus directe. Les verrous distribués avec Redis en PHP sont utiles lorsqu’ils coordonnent des processus qui ne peuvent pas être gérés par une seule opération atomique dans la base de données, à condition de les concevoir comme une protection temporaire et non comme la source de vérité définitive.

Ce qu’un verrou distribué résout et ce qu’il ne garantit pas

Ce qu’un verrou distribué résout et ce qu’il ne garantit pas — guía visual de DedicatedPHP

Un verrou distribué permet à plusieurs processus, éventuellement exécutés sur des serveurs différents, d’entrer en concurrence pour obtenir une exclusion temporaire. Si un worker acquiert le verrou, les autres devraient s’abstenir d’entrer dans la section critique tant qu’il est valide. C’est utile, par exemple, pour éviter que deux processus lancent simultanément une tâche coûteuse ou pour coordonner un travail qui concerne des services externes.

Le verrou ne garantit pas à lui seul qu’il n’y aura qu’une seule écriture, que le processus propriétaire est toujours actif, ni que Redis et la base de données conserveront un état cohérent. Il s’agit d’un droit temporaire, comparable à un lease. Si le worker s’arrête plus longtemps que la durée du verrou, Redis peut permettre à un autre de l’acquérir, même si le premier continue à travailler lorsqu’il reprend.

Une application ne devrait donc pas dépendre exclusivement de la clé Redis pour protéger une règle métier. L’opération finale doit disposer de ses propres protections : validations, contraintes d’intégrité, contrôle de version ou idempotence, selon le cas.

Quand utiliser Redis et quand choisir la base de données

Avant d’ajouter un verrou, identifiez la condition à préserver. S’il s’agit d’empêcher une entité d’avoir deux enregistrements portant le même identifiant, une contrainte d’unicité dans la base de données exprime cette règle et la fait respecter au point où les données sont écrites. Elle ne dépend pas du respect d’un protocole externe par tous les workers.

Si plusieurs modifications doivent être validées ensemble, une transaction peut suffire. Pour éviter d’écraser une mise à jour concurrente, le contrôle optimiste peut convenir : lire une version, mettre à jour uniquement si elle n’a pas changé et traiter explicitement le conflit. Pour les opérations portant sur des lignes ou des ensembles de données, le verrouillage pessimiste de la base de données elle-même peut être plus approprié, à condition d’en limiter la portée et la durée.

Redis peut être un bon choix lorsque l’exclusion coordonne le travail de plusieurs processus et qu’aucune opération unique de base de données ne permet de résoudre le problème, ou lorsqu’il faut éviter que de nombreux workers lancent simultanément une tâche répétitive. Évaluez également si le coût de l’attente, des nouvelles tentatives et de l’exploitation de Redis est justifié. Si une clé unique résout le problème avec moins de composants, elle est généralement préférable.

Acquisition atomique, propriétaire unique et expiration

L’acquisition doit être réalisée en une seule opération atomique : créer la clé uniquement si elle n’existe pas et lui attribuer une expiration au cours de la même opération. Dans Redis, le modèle habituel utilise SET avec les options équivalentes à « uniquement si la clé n’existe pas » et une durée de vie. Séparer la création de la clé et l’attribution de son expiration introduit une condition de concurrence : le processus pourrait échouer entre les deux opérations et laisser un verrou sans date d’expiration.

La valeur de la clé doit être un jeton unique généré pour cette tentative, et non une valeur constante partagée par tous les workers. Le jeton permet d’identifier le propriétaire et évite qu’un processus supprime par erreur le verrou acquis ensuite par un autre. Le client PHP ne doit considérer le verrou comme acquis que si Redis confirme la réussite de l’opération conditionnelle.

// Schéma conceptuel ; adaptez l’appel à la bibliothèque Redis utilisée.
$token = genererUnTokenUnique();
$acquis = redisSetSiAbsentAvecExpiration($cle, $token, $duree);

if (!$acquis) {
    // Ne pas entrer dans la section critique.
}

L’expiration doit dépasser la durée de travail prévue, sans être si longue qu’une panne fasse attendre inutilement les autres processus. Il n’existe pas de valeur universelle : mesurez la durée réelle, tenez compte de ses variations et définissez la marche à suivre pour les tâches qui approchent de la limite. Le renouvellement d’un lease exige également de vérifier que le jeton appartient toujours au processus ; un renouvellement aveugle pourrait prolonger le verrou d’un autre worker.

Libération sécurisée et risques liés à l’expiration

Ne supprimez pas simplement la clé à la fin du traitement. Si le verrou initial a expiré et qu’un autre processus en a acquis un nouveau, le premier worker pourrait supprimer la clé du second. La libération doit comparer la valeur stockée au jeton du propriétaire et supprimer la clé uniquement s’ils correspondent, dans le cadre d’une opération atomique. Cette opération peut être implémentée avec un script exécuté dans Redis ou une primitive équivalente qui compare et supprime sans intercaler d’autres opérations.

Même si l’acquisition et la libération sont correctes, une longue pause du processus peut entraîner des écritures concurrentes. Le worker A acquiert le verrou, est suspendu et son lease expire ; B l’acquiert et écrit ; A reprend et écrit à son tour. Pire encore, une partition réseau ou une panne de connexion peut empêcher le client de savoir si l’opération d’acquisition a été exécutée.

Si une écriture tardive peut causer des dommages, la ressource protégée doit la rejeter indépendamment. Une stratégie consiste à utiliser un numéro de fencing ou une version croissante et à exiger du système qui reçoit l’écriture qu’il rejette les valeurs anciennes. Ce compteur doit présenter les propriétés d’ordre et de durabilité requises par le cas ; il ne faut pas considérer automatiquement un compteur Redis comme une garantie sans tenir compte des redémarrages ou du failover. Une autre option consiste à vérifier une version dans la base de données au sein même de l’opération d’écriture.

Section critique, contention et échecs d’acquisition

Ne placez sous verrou que le travail qui exige réellement une exclusion. Évitez d’y inclure des appels lents à des services externes, des attentes liées à l’utilisateur ou des traitements indépendants. Plus la section critique dure, plus la contention augmente et plus le risque de dépasser le délai d’expiration est élevé. Si le travail peut être divisé, séparez la coordination de courte durée des tâches qui ne dépendent pas de l’état partagé.

Lorsqu’un verrou ne peut pas être obtenu, mieux vaut éviter les nouvelles tentatives indéfinies ou les longues attentes qui mobilisent des workers PHP. Définissez une politique en fonction de l’importance du travail : abandonner et consigner le conflit, réessayer après une attente limitée ou placer la tâche dans une file pour la traiter plus tard. La nouvelle tentative doit être sûre et idempotente, et le système doit distinguer une contention normale d’une panne de Redis.

En cas de connexion incertaine, ne supposez pas que le verrou a été acquis ou qu’il ne l’a pas été. Concevez le flux pour qu’il puisse récupérer sans exécuter deux fois une action irréversible : utilisez, par exemple, des clés d’idempotence pour l’opération métier et vérifiez son résultat avant de la répéter.

Tests et indicateurs opérationnels

Testez le comportement avec plusieurs workers et contrôlez les entrelacements, pas seulement le cas nominal. Incluez des scénarios où le processus se termine avant de libérer le verrou, où l’expiration survient pendant le traitement, où Redis ne répond pas et où un ancien worker tente de libérer le verrou après qu’un autre l’a acquis. Vérifiez que le jeton empêche la suppression d’une clé appartenant à un autre processus et que les écritures tardives sont rejetées lorsque l’application exige cette garantie.

En production, surveillez les échecs d’acquisition, la durée des sections critiques, les expirations, les renouvellements, les erreurs de connexion et les nouvelles tentatives. Une hausse du taux de contention peut indiquer que le verrou est défini à une granularité trop large ou que le travail effectué sous verrou est excessif. Des expirations fréquentes pendant des tâches normales signalent qu’il faut revoir la durée du lease ou la conception du processus, et non simplement augmenter automatiquement le TTL.

Liste de contrôle pour décider

Liste de contrôle pour décider — guía visual de DedicatedPHP
  • Quelle règle métier est protégée, et peut-elle être exprimée par une contrainte ou une transaction dans la base de données ?
  • L’acquisition est-elle atomique, conditionnelle et assortie d’une expiration ?
  • Chaque tentative dispose-t-elle d’un jeton unique, et la libération vérifie-t-elle le propriétaire avant la suppression ?
  • Que se passe-t-il si le processus est suspendu plus longtemps que le lease ou si Redis perd la connexion ?
  • Le système qui reçoit l’écriture peut-il rejeter les anciennes versions ou les doublons ?
  • Existe-t-il une politique limitée d’attente, de nouvelle tentative et de récupération, ainsi que des métriques et des tests de concurrence ?

Redis apporte une coordination temporaire, et non une garantie universelle de cohérence. Utilisez-le lorsque le problème nécessite réellement de coordonner des processus distribués, et maintenez la protection définitive dans le système qui écrit la donnée. Si la règle peut être imposée plus simplement dans la base de données, commencez par là.

Vous souhaitez appliquer ces idées à votre projet ?Parlons de votre plateforme PHP.
Afficher les services associés