Le WordPress d'aujourd'hui, décodé pour les développeurs

Performance

Trois requêtes redondantes trouvées par un audit de quarante extensions actives

Quarante extensions actives, chacune sûre de son bon droit : l'audit révèle trois requêtes strictement identiques exécutées sans concertation.

Par WordPress Développement • 28 octobre 2024 • 4 min de lecture • Aucun commentaire
Trois requêtes redondantes trouvées par un audit de quarante extensions actives

SELECT option_value FROM wp_options WHERE option_name = 'wpm_devise_active' : cette même ligne SQL, mot pour mot, apparaissait trois fois dans l’onglet Requêtes de Query Monitor, à chaque chargement de la page d’accueil. Sur un site associatif hébergeant quarante extensions actives, l’audit demandé après une plainte de lenteur a fini par ressembler à une enquête policière : qui interroge quoi, et pourquoi personne ne s’était aperçu que trois autres l’avaient déjà fait ?

L’option en question stockait la devise active du site, une valeur qui ne change presque jamais. Trois extensions distinctes — un module de dons, un widget d’affichage de prix et un connecteur de facturation — avaient chacune été codées indépendamment, par des auteurs différents, sans jamais imaginer qu’elles cohabiteraient sur le même site. Chacune appelait get_option( 'wpm_devise_active' ) à un moment différent du cycle de rendu de la page, ignorant que la valeur avait déjà été récupérée quelques millisecondes plus tôt par une autre.

Le piège du get_option qui semblait pourtant mis en cache

La première réaction a été de vérifier si l’option était autoloadée, ce qui aurait dû limiter la casse : une option avec autoload à yes est chargée une seule fois en mémoire par requête WordPress, via le cache d’objets non persistant. C’était bien le cas. Le problème ne venait donc pas de trois appels à get_option(), qui n’auraient généré qu’une seule requête SQL grâce au cache interne. La vraie cause était plus retorse : l’une des trois extensions appelait directement $wpdb->get_var() avec une requête manuelle, contournant complètement l’API des options et son cache.

Reconstituer le fil avec Query Monitor

L'essentiel à retenir : Chaque extension interrogeait la même donnée séparément ; Aucune ne connaissait l'existence des autres ; Un cache d'exécution a suffi à corriger le problème

L’onglet « Requêtes par composant » de Query Monitor a permis d’identifier précisément quelle extension exécutait quelle requête, grâce à la pile d’appels affichée pour chacune :

  • Le module de dons appelait get_option() normalement, sans souci.
  • Le widget de prix faisait de même, en bénéficiant donc du cache déjà chargé.
  • Le connecteur de facturation, plus ancien, requêtait directement la table wp_options via $wpdb, pour des raisons de compatibilité avec une version antérieure de WordPress où l’API des options se comportait différemment.

Ce contournement expliquait la troisième requête identique dans les logs, invisible pour qui ne regarde que le nombre total de requêtes sans descendre au niveau du composant responsable.

Un correctif sans toucher aux extensions tierces

Modifier le code d’une extension tierce pour la faire passer par get_option() aurait cassé la compatibilité à chaque mise à jour. La solution retenue a consisté à intercepter la requête directe grâce au filtre query, en réécrivant la clause pour la faire pointer vers une valeur déjà mise en cache dans une variable statique au sein d’un petit plugin maison :

add_filter( 'query', function ( $sql ) {
    static $cache = null;
    if ( false !== strpos( $sql, "option_name = 'wpm_devise_active'" ) ) {
        if ( null === $cache ) {
            $cache = get_option( 'wpm_devise_active' );
        }
        // La requête reste inchangée : on se contente de journaliser
        // et de vérifier la cohérence avec le cache local.
    }
    return $sql;
} );

En pratique, le correctif le plus efficace n’a pas été ce filtre de journalisation, mais un remplacement pur et simple : l’extension de facturation exposait un filtre propre, wpm_billing_get_currency, permettant de lui fournir directement la valeur déjà connue sans qu’elle ait besoin d’interroger la base.

Ce que l’audit a changé dans la méthode

Au-delà du cas précis de la devise, l’exercice a mis en évidence une limite structurelle des extensions WordPress : chacune est pensée comme si elle était seule sur le site. Aucune convention native ne permet à une extension de signaler « j’ai déjà cette donnée, prends-la chez moi » à une autre. L’audit a donc élargi son périmètre à toutes les options lues plus de deux fois par chargement de page, en croisant les résultats de Query Monitor avec un relevé manuel des appels à add_action( 'plugins_loaded' ) de chaque extension.

Un audit qui compte les requêtes sans identifier leur origine ne trouve que des symptômes ; celui qui remonte au composant responsable trouve des causes.

En résumé

Trois requêtes identiques, trois extensions qui s’ignoraient : le problème n’était ni un bug ni une négligence isolée, mais la conséquence naturelle d’un écosystème où chaque module est développé en vase clos. La correction a nécessité de sortir du réflexe « optimiser le cœur » pour aller inspecter, composant par composant, ce que chaque extension demandait réellement à la base de données.

Laisser un commentaire

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

Partager :

À propos de l'auteur

WordPress Développement

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi