Aller au contenu

De 393 à 85 requêtes SQL : optimiser une page WordPress avec Query Monitor et ACF

Visualisation abstraite de centaines de flux de données convergant vers un point unique, symbolisant la réduction des requêtes SQL WordPress par snapshot ACF

Mis à jour en 2026

Introduction

Une page Tarifs WordPress bien conçue côté éditorial peut devenir un cauchemar côté SQL. C’est exactement ce que j’ai constaté sur un projet récent : une page dense, administrée entièrement via ACF Pro sur une options page, qui générait à chaque chargement 393 requêtes SQL — dont 331 uniquement liées à la table wp_options.

Ni un problème d’hébergement, ni un plugin mal codé. Le vrai coupable : une architecture de lecture front parfaitement cohérente avec ACF, mais inadaptée à la densité du contenu.

Ce cas illustre pourquoi Query Monitor est indispensable dans une stack WordPress sérieuse, et pourquoi l’optimisation des get_field() ne suffit parfois pas. La bonne réponse est parfois architecturale.

Le contexte : une page Tarifs riche et administrable

La page en question agrégait plusieurs blocs de contenu :

  • 3 offres fixes avec tarifs, détails et appels à l’action
  • un estimateur pour les grands comptes
  • un tableau comparatif de fonctionnalités
  • des settings globaux (mentions légales, libellés, mise en avant)

Côté back-office, tout était géré via ACF Pro sur une options page — une approche confortable, qui évite de toucher au code pour mettre à jour un tarif ou ajouter une ligne dans le comparatif.

C’est exactement le cas d’usage pour lequel les options pages ACF ont été conçues.

Le diagnostic Query Monitor : 331 requêtes options en une page

En activant Query Monitor sur cette page et en filtrant l’onglet Queries sur wp_options, le constat est immédiat :

SELECT option_value FROM wp_options WHERE option_name = 'options_pricing_catalog_plans_0_name' LIMIT 1
SELECT option_value FROM wp_options WHERE option_name = 'options_pricing_catalog_plans_0_price' LIMIT 1
SELECT option_value FROM wp_options WHERE option_name = 'options_pricing_catalog_plans_0_features_0_label' LIMIT 1
...

331 requêtes de ce type, toutes issues d’ACF Pro lisant les champs de l’options page un par un.

La cause est mécanique : ACF stocke les repeaters et les groupes imbriqués sous forme d’entrées atomiques dans wp_options. Un repeater avec 3 plans, chacun contenant 8 sous-champs, génère 24 entrées minimum. Ajoutez un tableau comparatif avec 15 lignes et 5 colonnes, et vous dépassez facilement les 80 entrées rien que pour cette section.

Chaque appel front à get_field() sur une valeur non présente dans le cache objet déclenche une requête SELECT unitaire. Multipliez par le nombre de champs lus dans les templates, et le compteur Query Monitor s’emballe.

Ce comportement est documenté et attendu par ACF. Le problème n’est pas dans ACF en lui-même, mais dans l’usage : lire directement depuis les options ACF sur le front d’une page stratégique et dense.

Ce qui ne suffit pas : optimiser les get_field()

Le premier réflexe est souvent de regrouper les appels :

<?php
// Plutôt que lire champ par champ
$name  = get_field('plan_name', 'option');
$price = get_field('plan_price', 'option');

// Lire tout le groupe en une fois
$plan = get_field('pricing_catalog', 'option');

Ce pattern réduit le code, mais n’élimine pas les requêtes SQL : ACF résout quand même chaque sous-clé individuellement au moment de la lecture, si le cache objet n’est pas actif ou a été vidé.

Sur quelques champs simples, c’est acceptable. Sur une options page avec des repeaters imbriqués, le volume reste problématique.

La solution : un snapshot front agrégé

L’approche retenue préserve le confort éditorial ACF tout en changeant radicalement la stratégie de lecture front.

Principe

  1. L’admin continue d’éditer dans ACF — rien ne change côté back-office
  2. À chaque sauvegarde, un hook ACF normalise et sérialise tout le catalogue en une seule option WordPress dédiée
  3. Le front ne lit plus les clés options_* — il charge uniquement ce snapshot
<?php
// Hook déclenché à chaque sauvegarde ACF
add_action('acf/save_post', function ($post_id) {
    if ($post_id !== 'options') {
        return;
    }

    $snapshot = [
        'plans'      => get_field('pricing_catalog_plans', 'option'),
        'comparison' => get_field('pricing_comparison_rows', 'option'),
        'settings'   => get_field('pricing_settings', 'option'),
        'enterprise' => get_field('pricing_enterprise_block', 'option'),
    ];

    update_option('rl_pricing_front_snapshot_v1', $snapshot, true);
}, 20);
<?php
// Lecture front : une seule requête wp_options
function get_pricing_snapshot(): array {
    static $snapshot = null;
    if ($snapshot === null) {
        $snapshot = get_option('rl_pricing_front_snapshot_v1', []);
    }
    return $snapshot;
}

L’option est stockée avec autoload = true (troisième paramètre de update_option), ce qui signifie que WordPress la charge dans son autoload initial avec toutes les autres options — sans aucune requête SQL supplémentaire au moment de la lecture front.

Ce que ça change dans Query Monitor

Avant optimisation :

  • 393 requêtes SQL au total
  • 331 requêtes sur wp_options
  • Trace d’appel : 100 % ACF Pro via get_field()

Après optimisation :

  • 85 requêtes SQL au total
  • 35 requêtes sur wp_options
  • La page Tarifs ne génère plus aucune requête unitaire sur les champs ACF

La réduction est de -79 % sur les requêtes options et de -78 % sur le total. La latence perçue sur server-side rendering s’améliorait proportionnellement, surtout visible sur un environnement avec un serveur MySQL distant.

Checklist : identifier et corriger ce pattern

Signal dans Query MonitorCause probableCorrection
> 50 requêtes wp_options par pageOptions page ACF avec repeatersSnapshot sérialisé + autoload
Requêtes options_*_0_*_1_*Champs imbriqués lus à la voléeAgréger dans un snapshot à la sauvegarde
Mêmes clés options répétéesTemplate partiel appelé en boucleCache statique local dans le helper
Latence totale >> query timeBase distante (VPS, WP Engine…)Redis Object Cache pour éliminer le SQL
Volume croissant avec le contenuArchitecture couplée ACF ↔ frontDécoupler lecture et édition

Points clés à retenir

L’idée centrale de ce use case est simple, mais elle mérite d’être formulée clairement :

  • ACF est un excellent outil éditorial. Il n’est pas en cause.
  • Le vrai problème : utiliser des repeaters imbriqués sur une options page, puis lire ces champs directement sur le front d’une page dense.
  • La bonne séparation des responsabilités : ACF pour l’édition, une option normalisée pour la lecture.
  • Ce pattern est simple, robuste et maintenable — le snapshot est un tableau PHP sérialisé, lisible, verstonable, et rechargé à chaque sauvegarde admin.

Ce type de décision architecturale fait partie du travail courant que je propose dans mon activité de développeur indépendant : identifier les vrais goulots, pas seulement les optimiser en surface.

Aller plus loin : Redis et la suite

Le snapshot décrit ici élimine les requêtes SQL unitaires sur wp_options, mais repose toujours sur MySQL pour la requête autoload initiale. Sur des sites à fort trafic ou des architectures à plusieurs serveurs web, l’étape suivante naturelle est l’ajout d’un cache objet persistant avec Redis.

Avec Redis Object Cache, le snapshot est servi depuis la mémoire Redis dès la deuxième requête HTTP. WordPress ne touche plus MySQL du tout pour cette donnée. Query Monitor l’indiquera clairement : la requête disparaît du log, remplacée par un hit cache.

À venir : un article dédié à la mise en place de Redis sur WordPress — configuration, invalidation du cache, cas limites avec les options autoloadées, et mesure de l’impact réel avec Query Monitor.

Que se passe-t-il si l'admin oublie de sauvegarder la page options après une modification ?

Le snapshot ne se met à jour qu’au acf/save_post. En pratique, l’admin sauvegarde systématiquement pour valider ses modifications — c’est le comportement normal. En cas de doute, on peut ajouter un mécanisme de régénération manuelle via WP-CLI (wp eval "rl_rebuild_pricing_snapshot();") ou un bouton admin dédié.

Ce pattern est-il applicable à d'autres types de contenu ACF ?

Oui. Le même principe s’applique à tout contenu dense lu en lecture seule sur le front : menus de navigation enrichis, configurations de blocs Gutenberg globaux, listes de ressources administrées. Le critère de déclenchement est simple : dès que Query Monitor montre plus de 30-40 requêtes issues d’une options page, un snapshot est généralement justifié.

Le snapshot pose-t-il des problèmes de mise en cache de page (Varnish, WP Rocket) ?

Non, c’est l’inverse. Un snapshot accélère la génération de la page côté PHP avant même que le cache de page n’entre en jeu. Sur un cache de page full-page, la réduction des requêtes SQL améliore le TTFB mesuré sur les requêtes non servies depuis le cache (robots, première visite, cache expiré).

Besoin d'un accompagnement technique ?

On peut cadrer votre besoin WordPress rapidement et sans engagement.
Planifier une consultation

Conversation

0 commentaires

Une question, un retour d’expérience ou une nuance utile ? Ajoute ton point de vue.

Écrire un commentaire

Pas encore de commentaires. Lance la discussion.

Ton retour

Ton adresse e-mail ne sera pas publiée. Les champs marqués * sont obligatoires.