Passer au contenu
DedicatedPHP Contact

Invalidation de cache en PHP sans données obsolètes

Concevez un cache PHP qui accélère les lectures sans rendre obsolètes les autorisations, disponibilités, tableaux de bord ou configurations critiques.

Diagramme éditorial d’une application PHP qui invalide des clés de cache après la mise à jour de données dans la base de données

L’invalidation de cache en PHP ne consiste pas à choisir un TTL et à stocker des réponses dans Redis. C’est une décision de cohérence : déterminer quelles informations peuvent être retardées, pendant combien de temps et ce qui doit se produire lorsque la donnée d’origine change. Un cache mal conçu peut afficher un ancien prix, accorder un accès avec des autorisations révoquées ou présenter une disponibilité qui n’existe plus. Un cache trop conservateur, à l’inverse, reporte toute la charge sur la base de données et perd sa raison d’être.

Le point de départ consiste à traiter chaque entrée comme une donnée ayant un propriétaire, un cycle de vie et un risque définis. Cela permet aux équipes produit, métier et technique de convenir du moment où une lecture potentiellement obsolète est acceptable et de celui où l’état actuel de la source de vérité doit être obtenu.

Classifiez les données avant de les mettre en cache

Classifiez les données avant de les mettre en cache — guía visual de DedicatedPHP

Toutes les données fréquemment consultées ne doivent pas être mises en cache, et toutes n’admettent pas le même mécanisme. Évaluez chaque lecture selon quatre critères : volatilité, impact de l’obsolescence, coût de consultation de la source et tolérance à une défaillance du cache.

  • Faible volatilité et faible impact : les catalogues publics, métadonnées ou configurations non sensibles acceptent généralement des TTL de quelques minutes ou heures, selon leur processus de modification.
  • Volatilité moyenne : les fiches produit, agrégats de tableaux de bord et résultats de recherche peuvent être mis en cache s’ils sont invalidés lorsque les enregistrements qui les composent changent.
  • Impact élevé : les autorisations, soldes, limites, états transactionnels, stocks pendant la confirmation d’achat et contrôles d’autorisation nécessitent une source de vérité cohérente ou une stratégie explicite de fraîcheur très stricte.
  • Données coûteuses à calculer : les rapports et synthèses dérivés peuvent justifier un cache même s’ils sont peu consultés, mais ils doivent déclarer quelles entités les invalident.

Il est utile de séparer le cache de présentation du cache de décision. Afficher pendant quelques secondes l’ancien nom d’une catégorie peut être acceptable. Utiliser une ancienne politique pour autoriser une opération ne l’est généralement pas. Pour les décisions critiques, consultez la source faisant autorité ou stockez des versions qui peuvent être vérifiées avant d’agir.

Définissez la propriété, les clés et les contrats de fraîcheur

Chaque entrée a besoin d’une fiche opérationnelle. Elle doit indiquer la source de vérité, la clé, les consommateurs, le TTL maximal, l’événement d’invalidation, le comportement si Redis n’est pas disponible et le responsable fonctionnel ou technique. Sans ce contrat, les clés se multiplient et personne ne sait quoi supprimer après une modification.

Utilisez des clés prévisibles et d’une portée suffisante. Par exemple, product:42 représente une entité précise ; tenant:8:product:42 évite de mélanger les données entre organisations ; et dashboard:tenant:8:period:current identifie un résultat dérivé. N’incluez pas de données secrètes dans les clés et n’utilisez pas de sérialisations instables comme identité.

Il est également recommandé de conserver un format de valeur uniforme : charge utile, version ou date de génération et, lorsque cela est pertinent, un indicateur de fraîcheur. Un consommateur ne devrait pas supposer qu’une réponse mise en cache équivaut à une lecture cohérente sur le plan transactionnel.

final class ProductCacheKey
{
    public static function detail(int $tenantId, int $productId): string
    {
        return "tenant:{$tenantId}:product:{$productId}:v1";
    }
}

Le suffixe de schéma permet de modifier la structure de la valeur sans avoir à localiser et supprimer toutes les entrées historiques. Il ne remplace pas l’invalidation de la donnée métier, mais réduit les risques lors d’une évolution de format.

Choisissez le modèle de mise à jour selon le type de lecture

Cache-aside pour les lectures réutilisables

Avec le cache-aside, l’application recherche d’abord la clé ; en cas d’absence, elle interroge la base de données, construit la valeur et l’enregistre avec un TTL. C’est simple et adapté aux lectures relativement stables. Sa limite est claire : après une écriture, quelqu’un doit supprimer ou remplacer les entrées affectées.

L’invalidation doit se produire après la validation de la transaction. Supprimer avant le commit peut conduire un autre processus à reconstruire le cache avec l’ancienne valeur encore présente. Si l’application publie des événements, un modèle outbox aide à enregistrer la modification dans la même transaction et à délivrer ensuite l’ordre d’invalidation de manière fiable.

Mise à jour explicite et versionnage

Si une entité est lue très fréquemment et que ses modifications sont contrôlées, l’entrée peut être mise à jour après la validation de l’écriture. Cela évite le prochain cache miss. Toutefois, le processus doit générer exactement la même représentation que celle attendue par les lecteurs ; dans le cas contraire, invalider et reconstruire est généralement moins risqué.

Le versionnage des clés est utile pour les dépendances étendues. Au lieu de supprimer toutes les listes de produits d’une organisation, on incrémente tenant:8:products:version et les listes intègrent ce nombre dans leur clé. Les anciennes listes expirent d’elles-mêmes. Cette approche réduit les suppressions massives, mais exige de maîtriser la croissance des clés et ne doit pas servir à masquer une dépendance mal comprise.

Maîtrisez les conditions de course et les dépendances dérivées

La condition de course typique se produit ainsi : une lecture échoue dans le cache et consulte l’ancienne valeur ; une écriture est validée et invalide ; la première lecture se termine et enregistre de nouveau l’ancienne valeur. Pour les données sensibles, combinez l’invalidation avec une version d’entité ou un verrouillage bref de reconstruction. Avant d’écrire la valeur calculée, vérifiez que la version consultée est toujours la version actuelle. Si ce n’est pas le cas, écartez le résultat et relisez.

Les verrous distribués doivent être courts, avoir une expiration et ne pas devenir un point de blocage unique. Leur fonction est de réduire les reconstructions simultanées, et non d’assurer à eux seuls la cohérence métier. Si le verrou n’est pas acquis, une option consiste à attendre brièvement la valeur reconstruite ou à autoriser une lecture directe limitée.

L’invalidation par dépendance exige un inventaire. Une modification de produit peut affecter son détail, plusieurs listes, des résultats de recherche, des compteurs et un tableau de bord. Une modification de rôle peut affecter les autorisations effectives des utilisateurs et les menus dérivés. Modélisez explicitement ces relations :

  • Invalidez l’entité directe par sa clé.
  • Invalidez ou versionnez les collections et agrégats qui en dépendent.
  • Recalculez de manière asynchrone les résultats coûteux si l’expérience le permet.
  • Ne confondez pas le nettoyage d’une vue avec la mise à jour de la source de vérité.

Lorsque la relation n’est pas facilement énumérable, un espace de versions par organisation, catalogue ou politique est généralement plus sûr que d’essayer de découvrir toutes les clés concernées au moyen de modèles de suppression globale.

Utilisez TTL, jitter et limites pour protéger la source

Le TTL est un filet de sécurité, et non le seul mécanisme de cohérence. Même une clé correctement invalidée doit expirer : des défaillances de livraison d’événements, des erreurs de déploiement ou des entrées orphelines peuvent exister. Choisissez le TTL selon le coût de l’erreur, et non seulement selon le coût de la requête.

Appliquez un jitter aléatoire au TTL afin que des milliers de clés créées en même temps n’expirent pas simultanément. Protégez également la source contre une avalanche de cache misses grâce à un verrouillage de reconstruction par clé, des limites de concurrence et des quotas par consommateur. Pour les données non critiques, une valeur légèrement expirée peut être servie pendant qu’un seul processus la recalcule ; pour les autorisations ou la disponibilité décisionnelle, cette technique doit être écartée ou limitée à des scénarios expressément approuvés.

Concevez la dégradation lorsque Redis échoue

Redis est une dépendance opérationnelle, et non la source de vérité. S’il ne répond pas, l’application a besoin d’un mode de dégradation défini. Pour une lecture publique peu coûteuse, elle peut interroger directement la base de données avec des délais d’attente. Pour les requêtes coûteuses, il convient d’appliquer une limitation de charge, de réduire les champs, de répondre avec un état temporairement indisponible ou d’utiliser une réplique appropriée si l’architecture le prévoit.

Ne transformez pas une défaillance du cache en épuisement des connexions à la base de données. Définissez des timeouts courts, des circuit breakers, des budgets de requêtes et des métriques par route. Pour les données critiques, il est préférable de rejeter une opération plutôt que de prendre une décision avec des autorisations, soldes ou stocks dont la fraîcheur ne peut être garantie.

Testez et observez la fraîcheur, pas seulement les cache hits

Un taux de réussite du cache élevé ne prouve pas que le cache est correct. Instrumentez les réussites et échecs de cache, la latence, les erreurs de lecture et d’écriture, le TTL restant, les verrous de reconstruction, les invalidations émises et échouées, ainsi que les requêtes et la saturation de la base de données. Reliez ces signaux à chaque famille de clés, et pas seulement à Redis en tant que service global.

Dans les tests, couvrez au minimum la lecture initiale, la mise à jour ultérieure, la suppression, l’invalidation après commit, la panne du cache et les conditions de course entre lecteur et processus d’écriture. Vérifiez qu’un utilisateur perd l’accès après la révocation d’une autorisation, qu’une liste reflète une modification selon son contrat de fraîcheur et qu’une invalidation échouée déclenche des alertes ou une récupération.

Liste de contrôle pour une application PHP existante

Liste de contrôle pour une application PHP existante — guía visual de DedicatedPHP
  1. Énumérez les lectures répétées et classez-les par risque, volatilité et coût.
  2. Déclarez la source de vérité et la tolérance maximale à l’obsolescence de chaque donnée.
  3. Documentez les clés, TTL, dépendances, consommateurs et événement d’invalidation.
  4. Exécutez les invalidations ou mises à jour uniquement après le commit confirmé.
  5. Protégez les reconstructions simultanées et ajoutez du jitter aux expirations pertinentes.
  6. Définissez le mode dégradé en cas d’indisponibilité de Redis sans surcharger la base de données.
  7. Mesurez la fraîcheur et les invalidations, pas uniquement le taux de réussite du cache.
  8. Examinez périodiquement les clés sans propriétaire, les TTL excessifs et les dépendances non couvertes.

Une stratégie fiable d’invalidation de cache en PHP rend visibles ses compromis : ce qui peut devenir obsolète, pendant quel intervalle, comment cela est corrigé et ce qui se produit lorsqu’un composant échoue. Cette clarté a plus de valeur que d’ajouter un cache de manière indiscriminée.

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