Stratégie de mise en cache Redis: les coulisses d'un backend PHP performant
Une base relationnelle sollicitée sur chaque requête ne joue pas dans la même catégorie: elle doit parser, planifier, verrouiller, lire des index, reconstruire des lignes et parfois attendre une connexion disponible. Le cache ne supprime pas ces coûts. Il les évite sur le chemin critique.
Le choix du client PHP est le premier arbitrage. L’extension native PhpRedis peut être jusqu’à six fois plus rapide que Predis, bibliothèque entièrement écrite en PHP, grâce à son exécution binaire. Ce chiffre ne transforme pas automatiquement une application lente en application rapide. La sérialisation, le réseau, la taille des valeurs, le taux de succès du cache et la stratégie d’invalidation restent déterminants.
Une stratégie de mise en cache Redis PHP efficace repose donc sur cinq décisions liées:
- le pilote de connexion utilisé par le processus PHP;
- la granularité des clés et leur durée de vie;
- la politique d’éviction appliquée quand la mémoire atteint sa limite;
- le niveau de persistance attendu;
- le mécanisme d’invalidation lorsque la donnée source change.
Redis n’est pas une base relationnelle accélérée. C’est un composant mémoire avec ses propres contraintes d’allocation, de persistance et de saturation. L’ignorer revient à déplacer le goulot d’étranglement sans le supprimer.
PhpRedis ou Predis: le premier choix de performance
PhpRedis exécute moins de logique dans le moteur PHP
PhpRedis est une extension PECL écrite en C. Les appels sont exécutés au niveau natif, avec moins de surcharge liée à l’interpréteur PHP. Dans un backend à forte fréquence d’accès, cette différence se cumule: connexion, sérialisation, écriture de la commande, lecture de la réponse, conversion des types.
Le gain annoncé peut atteindre un facteur six par rapport à Predis. Il faut lire cette valeur comme un écart de benchmark, pas comme une promesse universelle sur le temps de réponse applicatif. Si une requête effectue déjà plusieurs appels réseau, interroge un service externe ou reconstruit une réponse volumineuse, le client Redis n’est qu’un élément du budget CPU.
PhpRedis devient le choix logique lorsque:
- l’application tourne sur des serveurs maîtrisés;
- l’installation d’une extension native est possible;
- le nombre d’opérations Redis par requête est élevé;
- la latence CPU du processus PHP est déjà observée dans les profils;
- le cache intervient dans les sessions, les verrous, les files de tâches ou les données métier chaudes.
La contrepartie est opérationnelle. Il faut installer et maintenir l’extension sur chaque environnement concerné: développement, intégration continue, préproduction, production et éventuellement les travailleurs asynchrones. Une image Docker PHP doit embarquer la même version d’extension que celle attendue par l’application. Une divergence de compilation ou de version peut créer un incident avant même le premier appel à Redis.
Predis réduit la friction d’installation
Predis est une bibliothèque PHP installable avec Composer, par exemple via composer require predis/predis. Elle ne demande pas de permission d’administration pour compiler une extension C. Dans un hébergement contraint, un environnement mutualisé ou un prototype, cette propriété est concrète. Le déploiement est plus simple.
Predis n’est pas inutilisable en production. Pour un trafic modéré, le coût de la bibliothèque peut rester inférieur au coût d’une opération d’infrastructure dédiée. La décision doit être prise à partir de mesures: temps CPU par requête, nombre d’opérations Redis, volume de données transféré et saturation des travailleurs PHP.
Le tableau résume l’arbitrage.
| Paramètre | PhpRedis | Predis |
|---|---|---|
| Nature | Extension native PECL en C | Bibliothèque entièrement écrite en PHP |
| Installation | Nécessite l’installation d’une extension | Installation directe via Composer |
| Performance brute | Jusqu’à six fois supérieure dans certains benchmarks | Inférieure sur les opérations répétitives et intensives |
| Contraintes d’hébergement | Accès nécessaire à la configuration PHP | Fonctionne sans compilation native |
| Cas d’usage pertinent | Production maîtrisée, forte fréquence d’accès, sessions, verrous | Hébergement limité, projet modéré, déploiement simplifié |
| Risque principal | Divergence entre environnements | Surcharge CPU et latence plus élevées à fort volume |
Le mauvais réflexe consiste à choisir Predis parce qu’il s’installe en une commande, puis à compenser plus tard avec davantage de processus PHP. Cette compensation augmente l’allocation mémoire, la pression sur le système et parfois le nombre de connexions simultanées à Redis. Le problème change de métrique, pas de nature.
Le pilote Redis n’est pas une préférence de style. C’est une décision sur le coût CPU de chaque accès mémoire.
Dans Laravel, le basculement reste explicite
Laravel fournit un support natif de Redis. Le fichier config/database.php permet de sélectionner le client avec la variable REDIS_CLIENT, généralement configurée sur phpredis ou predis.
Ce paramètre ne doit pas être modifié uniquement dans le fichier d’environnement de production. Le pipeline d’intégration doit tester le même chemin que celui déployé. Une application validée avec Predis puis exécutée avec PhpRedis peut révéler des différences de configuration, de sérialisation ou de gestion des options de connexion.
Le point de contrôle minimal comprend:
- la présence réelle de l’extension dans l’image PHP;
- la valeur effective de
REDIS_CLIENTaprès chargement de la configuration; - la résolution DNS ou l’adresse du serveur Redis;
- le port utilisé, 6379 par défaut;
- les délais d’expiration de connexion et de lecture;
- le comportement de l’application lorsque Redis devient indisponible.
Un cache ne doit pas transformer une panne Redis en panne totale du site, sauf si Redis porte une donnée indispensable au protocole de session ou à la cohérence métier. Le code doit distinguer un échec de lecture du cache d’un résultat vide. Ces deux états n’ont pas la même signification.
Concevoir l’espace mémoire avant de remplir Redis
Redis fonctionne principalement comme un magasin clé-valeur en mémoire. La vitesse vient de cette architecture. La limite vient du même endroit: la mémoire disponible n’est pas infinie, et chaque clé consomme plus que la seule taille visible de sa valeur.
Une clé longue, une valeur sérialisée volumineuse, des métadonnées nombreuses et une forte cardinalité peuvent saturer un serveur avant que les métriques applicatives ne le signalent clairement. Le calcul ne doit donc pas se limiter à additionner la taille des réponses métier.
Une clé doit décrire une donnée stable
Une clé Redis exploitable contient généralement:
- un préfixe d’application;
- un domaine fonctionnel;
- un identifiant;
- une version de schéma ou de représentation;
- éventuellement une région ou un contexte de lecture.
Un format tel que catalogue:produit:482:v2 est plus contrôlable qu’une concaténation ambiguë de paramètres. Le but n’est pas esthétique. Il faut pouvoir rechercher, invalider et migrer les clés sans dépendre d’une connaissance implicite du code.
La version est utile lors d’un changement de sérialisation ou de structure. Elle évite de mélanger une ancienne valeur avec une nouvelle représentation. Supprimer ou laisser expirer l’ancien espace devient alors une opération contrôlée.
Les clés doivent aussi éviter les collisions. Deux contrôleurs qui utilisent item:42 pour des objets différents créent un défaut de cohérence silencieux. Redis ne connaît pas le type métier de la valeur. Si la clé est identique, la collision est valide du point de vue du serveur.
Le TTL n’est pas une politique d’invalidation complète
Le TTL limite la durée de vie d’une valeur. Il ne garantit pas qu’elle soit fraîche entre deux écritures. Une fiche produit modifiée peut rester servie depuis le cache jusqu’à expiration si l’application ne supprime pas explicitement la clé.
Il faut distinguer deux catégories:
1. Les données tolérantes à la fraîcheur.
Une liste de contenus, une configuration de présentation ou un agrégat non critique peuvent accepter une durée de vie définie.
2. Les données sensibles à la cohérence.
Un droit d’accès, un stock, un état de commande ou une règle tarifaire ne doit pas dépendre d’un TTL utilisé comme unique mécanisme de validation.
Une durée de vie courte ne corrige pas une invalidation absente. Elle réduit seulement la fenêtre d’obsolescence. Pour une donnée fréquemment modifiée, elle peut aussi augmenter le taux de défaut du cache et déplacer la charge vers SQL.
Éviter le cache stampede
Lorsque plusieurs processus détectent simultanément une clé absente, ils peuvent exécuter la même requête coûteuse. Le cache est alors techniquement actif, mais la base subit une rafale exactement au moment où la valeur expire.
Les mécanismes courants sont connus:
- verrou distribué pendant la reconstruction;
- durée de vie légèrement décalée entre des clés similaires;
- rafraîchissement anticipé avant expiration;
- valeur obsolète servie temporairement pendant la reconstruction;
- regroupement des lectures dans une opération de récupération cohérente.
Le verrou lui-même doit avoir une expiration. Un processus PHP peut être tué avant de libérer son verrou. Sans TTL sur le verrou, le défaut de disponibilité devient persistant.
Il faut aussi limiter la taille des valeurs. Mettre en cache une réponse HTTP complète, avec des champs inutiles, augmente l’allocation mémoire et le coût de désérialisation. Le cache doit stocker la représentation réellement consommée par le chemin critique, pas une copie indiscriminée de l’objet métier.
Éviction mémoire: allkeys-lru n’est pas une assurance qualité
Redis propose des politiques d’éviction lorsque la limite mémoire est atteinte. allkeys-lru élimine les clés les moins récemment utilisées, qu’elles disposent ou non d’un TTL. C’est une politique adaptée à certains caches de lecture, mais pas à toutes les données.
L’erreur consiste à activer une éviction globale puis à stocker dans la même instance:
- des réponses temporaires;
- des sessions;
- des verrous;
- des files de traitement;
- des informations nécessaires à la cohérence métier.
Une politique LRU peut supprimer une donnée que l’application considère comme indispensable. Redis applique la règle configurée. Il ne connaît pas la criticité fonctionnelle.
Séparer les classes de données
Une architecture robuste sépare au moins les usages qui n’ont pas le même profil de perte:
- cache de lecture: données recalculables, éviction acceptable;
- sessions: perte potentiellement visible par l’utilisateur;
- verrous: durée de vie courte, récupération après expiration;
- files et événements: exigences de durabilité différentes;
- compteurs et quotas: dépendance forte à l’atomicité.
Cette séparation peut prendre la forme d’instances Redis distinctes, de services séparés ou de règles d’exploitation différentes. Une simple séparation par préfixe ne protège pas contre l’éviction si toutes les clés partagent la même limite mémoire.
La métrique utile n’est pas uniquement la mémoire consommée. Il faut suivre le taux de défaut du cache, le nombre d’évictions, les erreurs de connexion, la latence des commandes et la distribution des tailles de valeur. Une mémoire remplie à 80 % n’a pas la même signification selon que les valeurs sont stables ou que l’instance évince déjà des clés à haute fréquence.
La réduction de latence commence par le taux de succès
Un cache rapide avec un taux de succès faible reste une mauvaise architecture. Chaque défaut renvoie la requête vers SQL, souvent avec une reconstruction et une nouvelle écriture Redis. La charge cumulée peut dépasser celle du système sans cache.
Pour analyser le comportement, il faut distinguer:
- le temps de lecture Redis;
- le temps de calcul après un défaut;
- le temps de lecture SQL;
- le temps d’écriture de la valeur;
- le taux de défaut par clé ou par famille de clés;
- le volume de données récupéré par opération.
Une moyenne globale masque les défauts chauds. Une clé rarement lue et une clé appelée sur chaque requête ne doivent pas être agrégées dans la même métrique.
RDB ou AOF: la persistance ne rend pas Redis relationnel
Redis propose deux modes principaux de persistance: le snapshotting RDB et l’enregistrement AOF, pour Append-Only File. Ils ne répondent pas au même objectif.
RDB produit des instantanés de l’état Redis. Ce format convient à une restauration plus compacte et à des sauvegardes périodiques. Entre deux instantanés, des écritures peuvent ne pas être présentes dans le fichier restauré.
AOF enregistre les opérations d’écriture dans un fichier append-only. Il offre une trace plus détaillée des modifications, avec un coût d’écriture et de stockage qui dépend de la configuration et du volume d’opérations.
Le choix dépend de la nature de la donnée:
| Donnée hébergée dans Redis | RDB | AOF |
|---|---|---|
| Cache entièrement recalculable | Souvent suffisant, voire inutile selon le scénario | Généralement disproportionné |
| Session utilisateur | À évaluer selon la tolérance à la perte | Pertinent si la continuité de session est prioritaire |
| Compteur ou quota | Risque de perte à mesurer précisément | Plus adapté à une reprise détaillée |
| File de tâches | Dépend de la garantie attendue sur les tâches | À considérer avec une stratégie de reprise |
| Donnée métier temporairement critique | Ne pas choisir par défaut | À analyser avec le modèle de cohérence |
Un cache pur n’a pas forcément besoin d’être restauré. Si toutes les valeurs sont recalculables depuis la base principale, la persistance ajoute des écritures disque sans améliorer le résultat utilisateur. Elle peut même compliquer le redémarrage et prolonger la récupération d’un volume inutile.
À l’inverse, appeler toutes les données Redis du cache simplement parce que le composant est rapide crée une ambiguïté opérationnelle. Une clé utilisée comme source de vérité doit être traitée comme une donnée persistante, avec des exigences de sauvegarde, de reprise et de supervision. Elle ne peut pas être protégée par une politique d’éviction prévue pour des réponses jetables.
Si la perte d’une clé est inacceptable, cette clé n’est plus un simple élément de cache. Son modèle de persistance doit être explicite.
Intégrer Redis dans un backend PHP sans créer une dépendance aveugle
L’intégration la plus simple est le modèle cache-aside: l’application cherche la valeur dans Redis, interroge la source en cas d’absence, puis écrit le résultat avec un TTL.
Cette mécanique fonctionne tant que les chemins d’erreur sont définis. Que se passe-t-il si Redis ne répond pas? L’application doit-elle continuer sans cache? Les sessions peuvent-elles basculer vers un autre stockage? Les verrous doivent-ils interrompre l’opération? La réponse dépend du rôle de la donnée.
Il faut séparer les comportements:
- pour un cache de catalogue, un défaut Redis peut déclencher une lecture SQL;
- pour un verrou d’idempotence, l’échec doit souvent interrompre l’écriture;
- pour une session, un basculement silencieux peut créer deux états concurrents;
- pour une file de tâches, ignorer l’erreur peut perdre un traitement.
Le code métier ne devrait pas connaître les détails de connexion, de sérialisation et de préfixage. Une couche dédiée doit encapsuler la génération de clé, le TTL, la lecture, l’écriture et l’invalidation. Sans cette frontière, chaque contrôleur invente sa propre convention.
Sessions Redis PHP: rapidité et risque de concentration
Les sessions sont un cas d’usage fréquent. Redis centralise l’état entre plusieurs processus PHP et plusieurs nœuds applicatifs. Cette propriété facilite la montée en charge horizontale: une requête peut arriver sur un serveur différent sans perdre l’état de session.
Mais toutes les sessions concentrées sur la même instance augmentent la pression mémoire. Une politique d’éviction conçue pour un cache de pages peut déconnecter des utilisateurs si elle s’applique aux sessions. Le TTL doit correspondre à la durée de vie attendue, et la surveillance doit distinguer l’expiration normale d’une éviction forcée.
La sérialisation est également un coût. Les valeurs de session doivent rester compactes. Stocker un graphe d’objets PHP complet augmente la taille, le temps de conversion et le risque de rupture lors d’un changement de classe ou de version applicative. Une session doit contenir un état minimal: identifiant, permissions nécessaires, contexte court. Les données métier doivent rester dans leur stockage de référence.
Sérialisation: le détail qui modifie le profil CPU
JSON est lisible et interopérable, mais pas toujours optimal pour des structures PHP complexes. D’autres formats, comme Igbinary ou MsgPack, peuvent réduire la taille ou accélérer certaines conversions selon les données. Les gains précis dépendent de la version PHP, de la structure des valeurs et du matériel.
Il ne faut pas annoncer un gain universel sans benchmark représentatif. Le test doit utiliser:
- les tailles réelles de valeurs;
- les taux de lecture et d’écriture attendus;
- les mêmes options de compression;
- le même nombre de processus PHP;
- le même réseau entre l’application et Redis;
- des opérations de désérialisation représentatives.
Le temps mesuré doit être décomposé. Une valeur plus petite peut réduire le réseau mais augmenter le CPU de décodage. Une sérialisation plus rapide peut produire des valeurs plus volumineuses. Le meilleur format est celui qui respecte le budget global, pas celui qui gagne sur une micro-mesure isolée.
Invalidation: la partie difficile n’est pas GET
Lire une clé Redis est trivial. Déterminer quand elle ne doit plus être lue est le problème d’architecture.
Une application peut supprimer directement les clés associées à une entité modifiée. Cette méthode reste précise si la relation entre l’écriture et les clés dérivées est connue. Elle devient fragile lorsque la même donnée alimente plusieurs listes, agrégats, filtres et réponses API.
Les stratégies possibles ont des coûts distincts:
- invalidation ciblée: faible volume de suppression, forte discipline sur les dépendances;
- versionnement: changement d’un préfixe logique, nettoyage différé;
- expiration courte: implémentation simple, fraîcheur approximative;
- reconstruction événementielle: cohérence pilotée par les événements métier;
- purge par namespace: opération brutale, utile pour une migration ou une invalidation globale.
La suppression par motif est à manier avec prudence. Une opération qui parcourt un grand espace de clés peut monopoliser des ressources et dégrader Redis. Un namespace versionné est souvent plus prévisible: l’application écrit sous une nouvelle version et l’ancien espace expire progressivement.
Le cache distribué PHP doit aussi traiter les écritures concurrentes. Une requête peut lire une ancienne valeur pendant qu’une autre met à jour la base et invalide Redis. Sans ordre d’écriture clair, la valeur obsolète peut être réintroduite après l’invalidation par un processus qui avait commencé sa lecture avant la modification.
La solution n’est pas de multiplier les suppressions. Il faut définir la séquence:
1. effectuer l’écriture dans la source de vérité;
2. valider la transaction;
3. invalider ou versionner les clés dérivées;
4. reconstruire la valeur à la demande;
5. empêcher les lectures concurrentes de réintroduire l’ancien état.
Selon le niveau de cohérence requis, une invalidation asynchrone peut être acceptable. Elle ne l’est pas pour un droit d’accès ou une donnée financière si une fenêtre d’obsolescence crée un risque.
Redis ou Memcached pour PHP: le choix dépend des primitives utilisées
Redis et Memcached peuvent tous deux servir de cache mémoire, mais ils ne fournissent pas exactement le même ensemble de primitives. Redis ajoute des structures de données, des opérations atomiques, la persistance RDB ou AOF et des mécanismes utiles pour les verrous, compteurs ou files. Cette richesse augmente aussi la surface opérationnelle.
Memcached reste adapté à un cache simple de valeurs avec expiration, lorsque la persistance et les structures avancées ne sont pas nécessaires. Redis devient plus pertinent lorsque l’application utilise plusieurs fonctions au-delà du simple couple clé-valeur.
Le choix ne doit pas être dicté par la vitesse théorique d’une commande GET. Dans un backend PHP réel, les facteurs dominants sont souvent:
- la distance réseau entre PHP et le cache;
- le taux de succès;
- la taille des valeurs;
- la stratégie d’invalidation;
- le nombre de connexions;
- la saturation mémoire;
- la capacité de supervision;
- le comportement lors d’une panne.
Un Redis mal segmenté et mal surveillé peut être plus risqué qu’un Memcached limité mais correctement exploité. À l’inverse, remplacer Redis par Memcached ne résout pas une clé mal conçue, une requête SQL sans index ou une invalidation incohérente.
Mesurer avant d’optimiser le backend
La mise en cache doit produire des métriques exploitables, pas seulement une impression de rapidité. Une stratégie sérieuse suit au minimum:
- la latence Redis par commande et par percentile;
- le taux de succès et de défaut du cache;
- le nombre d’évictions;
- la mémoire consommée;
- la taille moyenne et maximale des valeurs;
- le nombre de connexions actives;
- les erreurs de connexion et les délais dépassés;
- la charge CPU des processus PHP;
- le temps SQL économisé ou déplacé.
Le temps de réponse global sous la milliseconde pour Redis ne signifie pas que l’API répondra sous la milliseconde. Le serveur HTTP, le framework, la sérialisation de la réponse, le réseau et le navigateur appartiennent au même chemin d’exécution.
Le benchmark pertinent reproduit le trafic attendu. Il doit comparer au moins:
- application sans cache;
- cache avec défaut fréquent;
- cache avec taux de succès élevé;
- PhpRedis;
- Predis;
- valeurs petites et valeurs représentatives;
- lecture seule et cycle lecture-écriture;
- Redis local et Redis distant.
La mesure doit inclure le garbage collector PHP et l’allocation mémoire du processus. Une valeur mise en cache peut réduire la charge SQL tout en augmentant la mémoire consommée par les travailleurs PHP lors de sa désérialisation. L’optimisation est globale ou elle est incomplète.
Verdict
PhpRedis à utiliser en production lorsque l’infrastructure PHP est maîtrisée, que le volume d’opérations Redis est significatif et que les mesures montrent un coût CPU ou une latence exploitable. Predis à utiliser en production lorsque la contrainte d’installation domine et que le trafic reste compatible avec sa surcharge.
Redis à utiliser comme cache si les données sont recalculables, les TTL sont définis, l’éviction est adaptée et l’invalidation est traitée comme une fonction d’architecture. Redis à ne pas utiliser comme pseudo-base de données lorsque la persistance, la cohérence et la reprise n’ont pas été spécifiées.
La bonne stratégie de mise en cache Redis PHP n’est donc pas celle qui ajoute le plus de clés. C’est celle qui réduit la charge SQL sans introduire une seconde source de vérité incontrôlable. Le verdict est binaire: cache distribué mesuré, segmenté et invalidé — à utiliser en production; cache ajouté sans modèle mémoire ni plan de reprise — à refuser.




