# wp_cache_add contre wp_cache_set : éviter l’effet de horde sur un cache chaud

> Quand un cache expire au mauvais moment, dix requêtes simultanées peuvent relancer dix fois le même calcul coûteux. Une simple différence de fonction change tout.

- Auteur : Clément Hadrot
- Publié le : 2020-11-20
- Mis à jour le : 2020-11-20
- Catégorie : Extensions
- URL : https://wpmoderne.dev.wordpress-developpement.fr/extensions/wp-cache-add-wp-cache-set-effet-horde/

## L’essentiel

- wp_cache_set écrase toujours, wp_cache_add échoue si la clé existe déjà
- Cette différence permet de bâtir un verrou anti-horde très simple
- Le calcul coûteux protégé, jamais son résultat mis en cache trop tard

Une extension d'un comparateur de prix calcule, pour chaque produit, un score de popularité à partir de plusieurs sources externes. Ce calcul prend environ 1,8 seconde et sollicite deux API tierces. Il est mis en cache pendant une heure via l'API objet de WordPress. Le problème : sur les pages les plus visitées, plusieurs dizaines de visiteurs arrivent dans la même seconde au moment précis où le cache expire, et chacun déclenche sa propre exécution du calcul, faute d'avoir trouvé de valeur en cache.

Le serveur encaisse alors, pendant quelques secondes, une charge équivalente à des dizaines de calculs simultanés au lieu d'un seul. C'est ce qu'on appelle l'effet de horde, ou *cache stampede* dans la littérature anglophone, et il touche n'importe quel cache qui expire brutalement sous forte charge, indépendamment de la technologie de stockage utilisée derrière l'objet cache.

## Deux fonctions qui se ressemblent, un comportement radicalement différent

L'API objet de WordPress propose deux fonctions d'écriture qui, à première lecture, semblent interchangeables : `wp_cache_set()` et `wp_cache_add()`. La première écrase systématiquement la valeur existante pour une clé donnée, qu'elle existe déjà ou non. La seconde, en revanche, échoue et retourne `false` si la clé est déjà présente dans le groupe de cache visé, sans rien modifier.

Cette différence, souvent ignorée parce qu'elle semble anecdotique, est justement ce qui permet de construire un verrou léger sans dépendre d'un système externe comme un verrou de base de données ou un sémaphore applicatif.

## La recette

> L'essentiel à retenir : wp_cache_set écrase toujours, wp_cache_add échoue si la clé existe déjà ; Cette différence permet de bâtir un verrou anti-horde très simple ; Le calcul coûteux protégé, jamais son résultat mis en cache trop tard

L'idée consiste à utiliser une clé de verrou distincte de la clé de données, posée avec `wp_cache_add()` juste avant de lancer le calcul coûteux. Seul le premier processus à arriver obtient ce verrou ; tous les suivants, tant que le verrou existe, servent une valeur légèrement périmée plutôt que de relancer le calcul :

```
function obtenir_score_popularite( int $produit_id ) {
    $cle_donnees = 'score_popularite_' . $produit_id;
    $cle_verrou  = 'verrou_score_' . $produit_id;
    $groupe      = 'comparateur';

    $valeur = wp_cache_get( $cle_donnees, $groupe );

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

    // On tente de poser un verrou de 30 secondes.
    $verrou_obtenu = wp_cache_add( $cle_verrou, 1, $groupe, 30 );

    if ( ! $verrou_obtenu ) {
        // Un autre processus calcule déjà : on sert une valeur de secours.
        $valeur_secours = get_transient( 'secours_score_' . $produit_id );
        return false !== $valeur_secours ? $valeur_secours : 0;
    }

    $valeur = calculer_score_popularite_depuis_api( $produit_id );

    wp_cache_set( $cle_donnees, $valeur, $groupe, HOUR_IN_SECONDS );
    set_transient( 'secours_score_' . $produit_id, $valeur, DAY_IN_SECONDS );

    return $valeur;
}
```

Le mécanisme tient en trois lignes clés : la lecture normale du cache, la tentative de verrou avec `wp_cache_add()`, et le repli sur une valeur de secours stockée séparément via un transient à durée de vie plus longue, qui sert de filet en cas d'expiration simultanée du cache principal.

### Pourquoi wp_cache_set ne peut pas jouer ce rôle

- Avec `wp_cache_set()`, chaque processus qui arrive écraserait le verrou du précédent, ce qui annule tout l'intérêt du mécanisme : il n'y aurait jamais qu'un seul « propriétaire » du calcul en cours.
- `wp_cache_add()` retourne un booléen exploitable directement dans une condition, sans lecture préalable supplémentaire.
- Le comportement de `wp_cache_add()` reste cohérent quel que soit le backend configuré derrière l'API objet, qu'il s'agisse du cache non persistant par défaut ou d'un serveur externe, tant que le backend respecte le contrat de l'API.

## Ce qu'il faut garder en tête

Cette technique protège contre l'effet de horde, elle ne remplace pas un cache d'objet persistant. Sur une installation qui n'a pas de serveur de cache externe configuré, l'API objet de WordPress reste non persistante par défaut : chaque requête PHP démarre avec un cache vide, et le verrou lui-même ne survit pas d'une requête à l'autre sans backend persistant. Le mécanisme décrit ici prend donc tout son sens uniquement sur une installation où un cache d'objet externe est actif.

> Un verrou qui ne peut être posé qu'une seule fois vaut mieux qu'un mutex qu'on doit surveiller soi-même.

## Prévention à plus grande échelle

Sur des volumes plus importants, on peut affiner la durée du verrou en fonction du temps réel d'exécution du calcul observé en production, plutôt que de fixer une valeur arbitraire. On peut aussi faire varier légèrement, de quelques secondes, la durée de vie du cache principal d'un produit à l'autre, pour éviter que des milliers de clés n'expirent exactement à la même seconde après un déploiement qui aurait réchauffé le cache en masse.

## En résumé

La différence entre `wp_cache_add()` et `wp_cache_set()` n'est pas un détail d'API : c'est le levier qui permet de transformer un cache fragile face à la charge en un cache résistant à l'effet de horde, sans ajouter de dépendance externe ni de complexité disproportionnée pour une extension qui protège un calcul coûteux.
