# Mandats immobiliers : un catalogue qui sature un serveur mutualisé

> Huit cents biens en vente, des filtres de recherche multiples, et un mutualisé qui commence à ralentir dès que plusieurs visiteurs consultent le catalogue en même temps.

- Auteur : WordPress Développement
- Publié le : 2021-11-10
- Mis à jour le : 2021-11-10
- Catégorie : Hébergement &amp; serveurs
- URL : https://www.wpmoderne.fr/hebergement/mandats-immobiliers-catalogue-sature-serveur-mutualise/

## L’essentiel

- Un grand nombre de fiches ne suffit pas seul à expliquer une lenteur
- Des requêtes meta_query mal indexées coûtent cher à grande échelle
- Un mutualisé peut absorber un catalogue volumineux si les requêtes sont maîtrisées

Huit cent quarante fiches de biens, chacune avec une dizaine de champs personnalisés — surface, nombre de pièces, prix, ville, type de mandat — et un formulaire de recherche permettant de combiner plusieurs de ces critères à la fois. Sur le papier, ce volume reste modeste. Dans les faits, le temps de génération d'une page de résultats filtrés dépassait fréquemment la seconde et demie, avec des pics au-delà de trois secondes lors des consultations simultanées.

Le premier réflexe, face à ce type de ralentissement, consiste souvent à conclure que le nombre de fiches dépasse ce qu'un hébergement mutualisé peut raisonnablement absorber. L'analyse des requêtes générées a montré une réalité différente : le volume de données n'était pas en cause, la façon de les interroger l'était.

## Ce que révèle l'analyse des requêtes lentes

L'activation du journal des requêtes lentes de MySQL, disponible même sur ce mutualisé via le tableau de bord de l'hébergeur, a mis en évidence des requêtes combinant plusieurs conditions `meta_query` sur des champs personnalisés non indexés, générées par le moteur de recherche du thème utilisé pour le catalogue.

```
SELECT wp_posts.* FROM wp_posts
INNER JOIN wp_postmeta AS mt1 ON wp_posts.ID = mt1.post_id
INNER JOIN wp_postmeta AS mt2 ON wp_posts.ID = mt2.post_id
INNER JOIN wp_postmeta AS mt3 ON wp_posts.ID = mt3.post_id
WHERE mt1.meta_key = 'prix' AND mt1.meta_value BETWEEN 150000 AND 300000
AND mt2.meta_key = 'ville' AND mt2.meta_value = 'Angers'
AND mt3.meta_key = 'nb_pieces' AND mt3.meta_value >= 3
```

Chaque critère de recherche supplémentaire ajoutait une nouvelle jointure sur la table `wp_postmeta`, une table par nature non optimisée pour ce type de filtrage combiné, puisqu'elle stocke toutes les métadonnées de tous les contenus dans une structure clé-valeur générique.

> L'essentiel à retenir : Un grand nombre de fiches ne suffit pas seul à expliquer une lenteur ; Des requêtes meta_query mal indexées coûtent cher à grande échelle ; Un mutualisé peut absorber un catalogue volumineux si les requêtes sont maîtrisées

## Pourquoi meta_query devient coûteux à cette échelle

Ce comportement n'est pas propre à cet hébergement : il s'agit d'une limite bien connue de la structure relationnelle de WordPress, où les champs personnalisés stockés via `add_post_meta` ne bénéficient pas d'index adaptés à des recherches combinées par plage de valeurs. Sur quelques dizaines de fiches, l'effet reste imperceptible. Sur plusieurs centaines, avec des filtres combinés, chaque jointure supplémentaire multiplie le coût de la requête.

## Les corrections apportées, par ordre d'impact

1. Création d'une table personnalisée dédiée aux critères de recherche fréquents (prix, ville, nombre de pièces), avec des index adaptés, alimentée à chaque enregistrement d'une fiche via le hook `save_post`
2. Réécriture du formulaire de recherche pour interroger cette table directement via `$wpdb`, plutôt que de passer par une `WP_Query` avec `meta_query` imbriquée
3. Ajout d'un cache de résultats pour les combinaisons de filtres les plus fréquentes, avec une durée de vie courte compte tenu de la mise à jour régulière du catalogue

## Le résultat mesuré après correction

| Indicateur | Avant correction | Après correction |
| --- | --- | --- |
| Temps moyen de génération | 1,8 s | 0,3 s |
| Requêtes MySQL par recherche filtrée | 4 à 6 jointures | 1 requête indexée |
| Charge PHP-FPM en heure de pointe | Pool régulièrement saturé | Marge confortable |

> Sur un mutualisé, la question n'est presque jamais « combien de fiches puis-je afficher ? » mais « combien de jointures ma requête de recherche génère-t-elle réellement ? ». Un catalogue de mille biens bien indexé consomme souvent moins de ressources qu'un catalogue de cent biens interrogé avec des `meta_query` combinées.

## Le hook retenu pour maintenir la table à jour

La table dédiée n'a d'intérêt que si elle reste synchronisée avec le contenu réel des fiches. Le choix technique s'est porté sur le hook `save_post`, filtré sur le type de contenu concerné, plutôt que sur une reconstruction périodique complète de la table, jugée trop coûteuse à mesure que le catalogue grandirait :

```
add_action( 'save_post_bien_immobilier', function ( $post_id ) {
    global $wpdb;
    $wpdb->replace(
        $wpdb->prefix . 'recherche_biens',
        array(
            'post_id'   => $post_id,
            'prix'      => get_post_meta( $post_id, 'prix', true ),
            'ville'     => get_post_meta( $post_id, 'ville', true ),
            'nb_pieces' => get_post_meta( $post_id, 'nb_pieces', true ),
        )
    );
} );
```

## Ce que ce dossier change pour les prochains catalogues

Aucune migration vers un serveur plus puissant n'a été nécessaire pour résoudre ce dossier. La leçon retenue porte sur la conception même du moteur de recherche : dès qu'un catalogue combine plusieurs critères de filtrage sur des champs personnalisés, une table dédiée correctement indexée devient préférable à une accumulation de `meta_query`, quelle que soit la puissance de l'hébergement sous-jacent.
