# Cache stampede sur Redis : le verrou qui évite l’effondrement au TTL expiré

> Quand un cache de page Redis expire en pleine heure de pointe, des dizaines de requêtes identiques frappent la base au même instant. Un verrou applicatif simple règle le problème.

- Auteur : Clément Hadrot
- Publié le : 2023-03-20
- Mis à jour le : 2023-03-20
- Catégorie : Performance
- URL : https://wpmoderne.dev.wordpress-developpement.fr/performance/cache-stampede-redis-verrou-ttl-expire/

## L’essentiel

- Un TTL identique pour tous les visiteurs fait expirer le cache au même instant
- Sans verrou, chaque requête simultanée régénère le contenu en parallèle
- Un verrou court avec expiration automatique évite ce doublon massif

7 h 30, un lundi matin, le tableau de bord de supervision d'un site d'actualité économique affiche un pic de charge CPU inexpliqué de quelques secondes, exactement au moment où le trafic commence à monter avec l'arrivée des lecteurs matinaux. Ce pic se reproduit chaque jour, toujours à peu près à la même fréquence, sans lien apparent avec un contenu publié ou une action précise d'un administrateur.

L'analyse des journaux Redis, croisée avec l'horodatage des pics observés, a fini par révéler la cause : l'expiration périodique d'une entrée de cache de page très consultée, la page d'accueil, réglée sur un TTL fixe de dix minutes, provoquait une ruée de requêtes identiques vers la base de données au moment exact de cette expiration.

## Comprendre le mécanisme du cache stampede

Le principe du cache de page est simple : la première requête après expiration régénère le contenu et le replace en cache, les requêtes suivantes profitant de cette version fraîche. Le problème survient quand plusieurs requêtes arrivent en parallèle, dans la fenêtre de quelques dizaines de millisecondes où le cache vient d'expirer mais où aucune requête n'a encore fini de régénérer le contenu. Chacune de ces requêtes constate un cache absent et déclenche, en parallèle, sa propre régénération complète, avec ses propres requêtes SQL coûteuses.

Sur ce site, aux heures de pointe, une quarantaine de requêtes arrivaient couramment dans cette fenêtre critique de quelques dizaines de millisecondes, ce qui signifiait quarante-sept exécutions parallèles et redondantes de la même logique de génération de page, mesurées un matin via les journaux Redis horodatés à la milliseconde.

## Pourquoi un TTL plus long n'est pas la solution

Augmenter simplement le TTL réduit la fréquence du phénomène sans l'éliminer : il finira toujours par se reproduire, avec potentiellement un trafic encore plus élevé au moment de l'expiration si le TTL est plus long. La vraie solution consiste à s'assurer qu'une seule requête régénère le contenu à la fois, les autres attendant ou servant une version légèrement périmée en attendant.

> L'essentiel à retenir : Un TTL identique pour tous les visiteurs fait expirer le cache au même instant ; Sans verrou, chaque requête simultanée régénère le contenu en parallèle ; Un verrou court avec expiration automatique évite ce doublon massif

## L'implémentation du verrou applicatif

Le correctif repose sur une clé de verrou distincte de la clé de cache elle-même, posée dans Redis avec une très courte expiration automatique, via la commande native `SET` avec l'option `NX` pour ne poser le verrou que s'il n'existe pas déjà :

```
function obtenir_page_avec_verrou( $cle_cache ) {
    $contenu = wp_cache_get( $cle_cache, 'pages' );

    if ( false !== $contenu ) {
        return $contenu;
    }

    $cle_verrou = $cle_cache . '_verrou';
    $verrou_pose = wp_cache_add( $cle_verrou, 1, 'pages', 5 );

    if ( ! $verrou_pose ) {
        // Un autre processus régénère déjà le contenu : on sert l'ancienne version si elle existe encore.
        usleep( 100000 );
        return wp_cache_get( $cle_cache, 'pages' ) ?: generer_page_directement();
    }

    $contenu = generer_page_directement();
    wp_cache_set( $cle_cache, $contenu, 'pages', 600 );
    wp_cache_delete( $cle_verrou, 'pages' );

    return $contenu;
}
```

`wp_cache_add()` est ici le choix délibéré et correct : son comportement de non-écrasement, décrit ailleurs comme source de bug quand mal utilisé, devient exactement la propriété recherchée pour un verrou, un seul processus devant réussir à poser cette clé.

## Le résultat mesuré

Après déploiement, les journaux Redis ont confirmé qu'une seule régénération de contenu se produisait désormais par expiration de cache, les autres requêtes parallèles patientant brièvement puis récupérant le contenu fraîchement régénéré. Le pic de charge CPU matinal a disparu des graphiques de supervision dans les jours suivant le déploiement.

- Toujours prévoir une expiration automatique courte sur la clé de verrou, pour ne jamais bloquer durablement la régénération en cas d'erreur.
- Préférer `wp_cache_add()` pour poser un verrou, exactement pour son comportement de non-écrasement.
- Tester le comportement sous charge réelle, un verrou mal calibré peut lui-même devenir un point de contention.

## Pour aller plus loin

Ce correctif suppose un backend Redis déjà en place et correctement configuré, sa mise en place initiale n'étant pas traitée ici. Le principe du verrou applicatif reste néanmoins transposable à tout backend de cache d'objet persistant offrant une opération atomique équivalente à `SET NX`.
