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’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.