WordPress: Développer son propre système de cache

Même si le système n’était pas parfait, le cache interne de WordPress, présent jusqu’à la version 2.3 me semblait intéressant. D’un point de vue technique j’étais également curieux de voir comment se développe un système de cache avec WordPress. Je vous propose donc quelques informations sur le sujet.
Cet article est plus une curiosité qu’autre chose. La solution proposée ne prétend absolument pas remplacer les plugins détaillés dans l’article précédent.

Structure

Dans WordPress, tout commence par wp-settings.php. Ce fichier est exécuté au tout début du processus de construction des pages (globalement à chaque clic). Si l’on analyse ce fichier, nous remarquons les trois blocs de code suivants:

// For an advanced caching plugin to use, static because you would only want one
if ( defined('WP_CACHE') )
	@include WP_CONTENT_DIR . '/advanced-cache.php';

Puis:

if ( file_exists(WP_CONTENT_DIR . '/object-cache.php') )
	require_once (WP_CONTENT_DIR . '/object-cache.php');
else
	require_once (ABSPATH . WPINC . '/cache.php');
 
wp_cache_init();

Et enfin:

if ( defined('WP_CACHE')  && function_exists('wp_cache_postload') )
	wp_cache_postload();
do_action('plugins_loaded');

Pour résumer:

  • Nous avons deux systèmes de cache: un cache objet et un cache avancé
  • Pour le cache objet, WordPress charge systématiquement le fichier wp-includes/cache.php, sauf s’il trouve le fichier wp-content/object-cache.php,
  • Le cache avancé est activé si la constante WP_CACHE est initialisée à TRUE
  • Si le cache avancé est activé, WordPress charge le fichier wp-content/advanced-cache.php, puis exécute la fonction wp_cache_postload

Ces deux types de cache ne jouent pas tout à fait le même rôle:

  • Le cache objet va réduire les temps d’accès aux données, en évitant, par exemple, de relancer des requêtes à la base de données,
  • Le cache avancé s’attache à réduire le temps de génération des pages. Ce temps inclut l’accès ET le traitement des données. Le cache avancé est donc plus efficace.

Développement

Si nous voulons développer notre propre système de cache, il faut:
Dans le cas d’un cache objet

  • Créer un fichier wp-content/object-cache.php,
  • Construire le nouveau système de cache, en surchargeant la classe WP_Object_Cache, et réécrivant les méthodes add, set, get, delete …,

Dans le cas d’un cache avancé

  • Créer le fichier wp-content/advanced-cache.php,
  • Créer la fonction wp_cache_postload dans ce fichier,
  • Ecrire les fonctions nécessaires au fonctionnement du cache, l’appel s’effectuant à partir de wp_cache_postload.

Un cache « objet » en 5 minutes

L’opération suivante n’est absolument pas officielle. Je déconseille fortement pour un blog en production. Elle ne fonctionnera pas si vous avez déplacé votre répertoire wp-content.

Mise en place

Pour mettre en place un cache objet rapidement, il suffit de:

  • Télécharger la version 2.3.3 de WordPress,
  • Décompresser l’archive, et en extraire le fichier wp-includes/cache.php,
  • Placer ce fichier dans le répertoire wp-content en le renommant en object-cache.php ,
  • Modifier le fichier wp-config.php, en y ajoutant la ligne define('ENABLE_CACHE', TRUE);.

Et voilà !!!

J’ai effectué quelques tests avec les versions 2.5.x, 2.6.2, et 2.7 sans constater d’anomalies. Lorsque le cache fonctionne, le nombre de requêtes à la base de données passe de 25 à 4. Au moment du renouvellement du cache, le nombre de requêtes repasse à 25. Nous avons donc le même inconvénient que le cache original.

Tentative d’optimisation

Pour pallier à cet inconvénient, il faut comprendre comment fonctionne le cache:

  • Lors du développement d’une extension (thème, plugin, widget), nous pouvons utiliser les commande wp_cache_get et wp_cache_set pour prendre en compte le cache,
  • Sauf mention explicite, le délai de conservation de données est soit 900 secondes (15 minutes), soit le délai fixé par la constante CACHE_EXPIRATION_TIME dans le fichier wp-config.php,
  • Cela veut dire globalement que, sans spécifications contraires, toutes les données « cachées » sont renouvelées en même temps (au bout du même délai),

Une façon rapide de contourner le problème, tout en restant compatible avec le système actuel, est d’ajouter une composante aléatoire au délai d’expiration.

En ligne 157 du fichier wp-content/object-cache.php, nous trouvons le code:

// If the object has expired, remove it from the cache and return false to force
// a refresh.
$now = time();
if ((filemtime($cache_file) + $this->expiration_time) <= $now) {
	$this->cache_misses += 1;
	$this->delete($id, $group, true);
	return false;
}

Que nous pouvons remplacer par

// If the object has expired, remove it from the cache and return false to force
// a refresh.
$coeff = 10;
$new_expiration_time = mt_rand($this->expiration_time - ($this->expiration_time/$coeff),
                               $this->expiration_time+($this->expiration_time/$coeff));
$now = time();
if ((filemtime($cache_file) + $new_expiration_time) <= $now) {
	$this->cache_misses += 1;
	$this->delete($id, $group, true);
	return false;
}

J’ai remplacé la durée d’expiration fixe, par une durée d’expiration variable qui oscille entre -10% et +10% de la valeur fixée initialement. Si la valeur est 900 secondes, nous aurons donc une durée pouvant varier de 810 secondes à 990 secondes, soit de 13m30s à 16m30s.

Pour évaluer les gains potentiels, j’ai testé la solution, en simulant un clic régulier (tous les 30s avec la commande wget) sur différents liens du blog pendant 40 minutes, avant, puis après la modification, ce qui donne le graphique ci-dessous:

La courbe Optimisé 1 correspond à un coefficient de 10%. La courbe Optimisé 2 correspond à un coefficient de 20%.

Sans le cache, le nombre de requêtes oscille en permanence entre 26 et 35. Avec le cache standard de la version 3.3, le nombre de requêtes descend de 4 à 8, sauf pendant le renouvellement du cache ou nous revenons au nombre initial.
Avec la modification sur les temps d’expiration, les pics sont moins élevés, nous avons bien un lissage du nombre de requêtes. Ce lissage est cependant beaucoup moins important que prévu. De plus, si l’on calcule la moyenne du nombre de requêtes passées dans les trois cas, nous constatons que les différences sont vraiment minimum.

Méthode Nombre moyen
de requêtes
Cache standard 8.13
Cache optimisé 10% 8.33
Cache optimisé 20% 7.65

Ma méthode d’optimisation n’est donc pas suffisamment efficace pour être retenue.

Conclusion

Au risque de me répéter, je suis toujours étonné de la simplicité et de la robustesse de WordPress du point de vue du développeur. La « bidouille » décrite dans cet article en est la preuve.
Comme précisé, cet article est plus un curiosité qu’autre chose. Cette petite étude m’a cependant permis de comprendre les différents type de cache, et leur fonctionnement. Les quelques tests réalisés montrent notamment les avantages et inconvénients de l’ancien cache interne de WordPress.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.