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

Headless & API

200 000 lignes de postmeta scannées par une route REST faute d’un index

Une route REST personnalisée ralentit progressivement à mesure que le contenu grandit. En cause, une meta_query qui force un scan complet de la table postmeta.

Par WordPress Développement • 4 janvier 2023 • 5 min de lecture • Aucun commentaire
200 000 lignes de postmeta scannées par une route REST faute d'un index

200 000 lignes lues, 4 lignes retournées : c’est ce que révèle une analyse EXPLAIN lancée sur la requête générée par une route REST maison qui filtre des fiches produits selon un statut personnalisé. La route fonctionnait sans accroc à ses débuts, quand le catalogue comptait quelques centaines de références. Un an plus tard, avec plusieurs dizaines de milliers de fiches, chaque appel prend plus d’une seconde, et le temps continue de grimper avec le volume de contenu.

Ce scénario est classique sur les projets headless qui exposent un filtrage par métadonnée personnalisée : la route fonctionne, les résultats sont corrects, et rien dans les tests fonctionnels ne signale de problème. Seule la charge réelle en production, avec un volume de données représentatif, révèle le défaut de conception.

Le diagnostic : une meta_query sans filtre efficace

La route en question s’appuie sur WP_Query avec un argument meta_query portant sur une clé personnalisée, par exemple statut_stock :

$query = new WP_Query( array(
    'post_type'  => 'produit',
    'meta_query' => array(
        array(
            'key'     => 'statut_stock',
            'value'   => 'disponible',
            'compare' => '=',
        ),
    ),
    'posts_per_page' => 20,
) );

Sur le papier, rien d’anormal. Le problème vient de la structure même de la table wp_postmeta : elle stocke toutes les métadonnées de tous les types de contenus, sous forme clé-valeur, avec un index par défaut qui ne couvre efficacement que la colonne meta_key seule. Dès que le volume de lignes dépasse plusieurs dizaines de milliers, MySQL doit lire un nombre croissant de lignes correspondant à la clé avant de filtrer sur la valeur.

Lire un plan d’exécution avec EXPLAIN

La commande EXPLAIN, exécutée directement sur la requête générée, confirme le diagnostic :

EXPLAIN SELECT wp_posts.ID FROM wp_posts
INNER JOIN wp_postmeta ON wp_posts.ID = wp_postmeta.post_id
WHERE wp_postmeta.meta_key = 'statut_stock'
AND wp_postmeta.meta_value = 'disponible';
L'essentiel à retenir : Une meta_query sans index adapté force un scan de table complet ; Un index composite sur meta_key et meta_value réduit le temps de réponse ; EXPLAIN permet de vérifier qu'une requête utilise bien l'index créé

La colonne rows du résultat affiche un nombre proche du total de lignes de la table postmeta, signe que l’index utilisé ne réduit pas suffisamment l’espace de recherche. La colonne Extra mentionne souvent Using where, confirmant qu’un filtrage supplémentaire s’opère après la lecture, ligne par ligne.

Le correctif : un index composite ciblé

La solution consiste à créer un index couvrant à la fois meta_key et meta_value, directement sur la table concernée :

ALTER TABLE wp_postmeta
ADD INDEX idx_statut_stock (meta_key, meta_value(20));

Le préfixe de longueur sur meta_value reste nécessaire car cette colonne est de type longtext, que MySQL ne peut indexer intégralement. Une longueur de préfixe de 20 caractères suffit largement pour une valeur courte comme un statut, mais doit être ajustée selon la nature réelle des données stockées dans cette clé.

Après création de l’index, le même EXPLAIN affiche un nombre de lignes lues proche du nombre de résultats réellement retournés, et le temps de réponse de la route redescend à quelques dizaines de millisecondes.

Une alternative plus radicale : sortir la donnée de postmeta

Pour les métadonnées interrogées très fréquemment, l’ajout d’un index reste une rustine efficace mais pas toujours suffisante à long terme. Plusieurs options structurelles existent :

  • Stocker la valeur dans une taxonomie personnalisée plutôt qu’un champ personnalisé, ce qui bénéficie de l’indexation native de wp_term_relationships.
  • Créer une table dédiée via dbDelta() pour les données interrogées en masse et à fort volume.
  • Ajouter une couche de cache applicatif, avec wp_cache_get() et wp_cache_set(), en amont de la requête coûteuse, quand la fraîcheur absolue n’est pas critique.

Prévenir la récidive

Un index bien pensé aujourd’hui ne garantit rien pour un futur champ personnalisé ajouté sans vigilance équivalente. Intégrer une revue systématique des plans d’exécution des routes REST maison avant leur mise en production, dès qu’elles s’appuient sur une meta_query, évite de reproduire le même scénario sur la prochaine fonctionnalité.

Une route REST qui répond vite avec 500 fiches en environnement de recette ne dit rien de son comportement avec 50 000 fiches en production. Le volume de données de test doit refléter l’ordre de grandeur réel, sans quoi le problème n’apparaît jamais avant la mise en ligne.

Pour aller plus loin

La documentation officielle de WP_Meta_Query détaille les arguments disponibles pour affiner un filtrage sur les métadonnées, mais elle ne remplace pas une vérification du plan d’exécution SQL sous-jacent. Sur un projet headless amené à grandir, cette vérification mérite d’entrer dans la liste des contrôles systématiques avant chaque mise en production d’une nouvelle route.

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