vendredi 25 septembre 2026

À propos

Contact

Extensions

Migrer une extension de CPT vers des tables personnalisées : retour chiffré

Retour d'expérience sur la migration d'un CPT stockant 2 millions de lignes vers des tables personnalisées : script de migration, compatibilité ascendante et gains mesurés.

Par Clément Hadrot • 21 avril 2025 • 6 min de lecture • Aucun commentaire
Migrer une extension de CPT vers des tables personnalisées : retour chiffré

L’extension en question enregistre des relevés de capteurs pour des exploitations agricoles : température, humidité, niveau d’irrigation, un relevé toutes les quinze minutes par capteur, sur plusieurs centaines de capteurs installés chez les clients d’un fabricant de matériel connecté. Conçue initialement comme un CPT releve_capteur avec les valeurs stockées en post meta, l’extension a fonctionné sans problème pendant les deux premières années. Puis le volume a grimpé : à raison de plusieurs dizaines de milliers de relevés par jour toutes exploitations confondues, la table wp_postmeta a fini par dépasser deux millions de lignes rien que pour ce CPT, et les tableaux de bord d’analyse ont commencé à mettre plusieurs secondes à s’afficher.

Ce retour d’expérience détaille la migration effective vers des tables personnalisées, sans revenir sur les bases de dbDelta() déjà couvertes ailleurs — l’objectif ici est le récit de la bascule elle-même, ses risques, et ses résultats mesurés.

Diagnostiquer avant de migrer

Avant de se lancer, on a profilé les requêtes les plus lentes avec SAVEQUERIES activé sur un environnement de test chargé avec un export réel de la production. Le constat : chaque affichage de tableau de bord déclenchait une WP_Query avec des meta_query combinées sur trois champs différents (capteur, plage de dates, seuil d’alerte), ce qui générait des jointures multiples sur wp_postmeta — une table qui n’est simplement pas conçue pour ce type de filtrage combiné à ce volume, faute d’un schéma adapté aux relevés numériques et temporels.

Conception du nouveau schéma

L'essentiel à retenir : La migration s'est faite en double écriture avant bascule totale ; Une couche de compatibilité a évité de réécrire tout le code appelant ; Le temps de requête moyen a été divisé par un facteur mesuré sur nos rapports
CREATE TABLE {$wpdb->prefix}capteurs_releves (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    capteur_id BIGINT UNSIGNED NOT NULL,
    exploitation_id BIGINT UNSIGNED NOT NULL,
    releve_a DATETIME NOT NULL,
    temperature DECIMAL(5,2) NULL,
    humidite DECIMAL(5,2) NULL,
    niveau_irrigation DECIMAL(5,2) NULL,
    PRIMARY KEY (id),
    KEY capteur_date (capteur_id, releve_a),
    KEY exploitation_date (exploitation_id, releve_a)
) {$wpdb->get_charset_collate()};

Les colonnes typées (DECIMAL plutôt que des chaînes sérialisées) et les index composites sur les couples réellement interrogés (capteur + date, exploitation + date) sont la vraie source du gain : une jointure sur wp_postmeta ne peut par nature pas offrir ce niveau de sélectivité, quel que soit le soin apporté à l’indexation existante.

La migration en double écriture

Migrer deux millions de lignes en une seule opération, en coupant le service, n’était pas acceptable pour un client dont les alertes d’irrigation doivent rester actives en continu. La stratégie retenue s’est faite en trois phases étalées sur plusieurs semaines :

  1. Double écriture : chaque nouveau relevé continue d’être enregistré comme avant (CPT et post meta), mais est aussi écrit dans la nouvelle table, via un hook sur la fonction d’enregistrement existante. Aucune lecture ne change encore.
  2. Migration de l’historique en tâche de fond : un traitement par lots de 2 000 lignes, exécuté via une tâche planifiée toutes les minutes, copie progressivement l’historique complet vers la nouvelle table, avec suivi de progression en option pour permettre une reprise en cas d’interruption.
  3. Bascule des lectures : une fois l’historique entièrement migré et vérifié (comparaison de comptages et de sommes de contrôle entre les deux structures), le code de lecture des tableaux de bord est basculé vers la nouvelle table. L’écriture dans l’ancien CPT est arrêtée dans la foulée.

Une couche de compatibilité pour éviter une réécriture totale

Plutôt que de réécrire chaque appel existant dans le code de l’extension, une classe de façade a été introduite, avec la même interface publique que les fonctions historiques basées sur le CPT, mais une implémentation interne redirigée vers la nouvelle table :

class Capteurs_Releves_Repository {
    public function obtenir_releves( int $capteur_id, DateTimeImmutable $depuis, DateTimeImmutable $jusqua ): array {
        global $wpdb;
        return $wpdb->get_results( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}capteurs_releves
             WHERE capteur_id = %d AND releve_a BETWEEN %s AND %s
             ORDER BY releve_a ASC",
            $capteur_id,
            $depuis->format( 'Y-m-d H:i:s' ),
            $jusqua->format( 'Y-m-d H:i:s' )
        ) );
    }
}

Les modules d’affichage et les rapports existants, qui appelaient auparavant une fonction capteurs_obtenir_releves() adossée au CPT, ont simplement vu cette fonction redirigée vers la nouvelle classe, sans toucher au code appelant. Le coût de la migration côté code applicatif s’est ainsi limité à la couche d’accès aux données, pas à l’ensemble des écrans.

Résultats mesurés

IndicateurAvant (CPT + postmeta)Après (tables dédiées)
Temps moyen d’affichage d’un tableau de bord mensuelPlusieurs secondes, dégradation continue avec le volumeTemps stable, quasi constant quel que soit le volume historique
Taille de wp_postmetaEn croissance continue et dominante sur la baseAllégée, le CPT n’est plus utilisé pour ce volume
Complexité des requêtes de rapportmeta_query imbriquées, difficiles à maintenirSQL direct avec index dédiés, plus lisible

Le point qu’on retiendrait le plus de ce projet : la double écriture temporaire a coûté un peu de complexité pendant quelques semaines, mais elle a rendu la bascule totalement transparente pour le client — aucune interruption de service, aucune perte de relevé, et une possibilité de revenir en arrière à tout moment tant que l’ancien CPT restait alimenté.

En résumé

Migrer un CPT à fort volume vers des tables personnalisées n’est pas une décision à prendre à la légère, mais elle devient nécessaire dès que wp_postmeta montre ses limites structurelles sur un schéma de données répétitif et fortement interrogé par plage temporelle. La clé du succès sur ce projet a été la progressivité : double écriture, migration de l’historique en tâche de fond, puis bascule des lectures une fois la cohérence vérifiée — jamais une coupure brutale, jamais une réécriture complète du code appelant grâce à une couche de compatibilité assumée comme temporaire mais fonctionnelle.

Partager :

À propos de l'auteur

Clément Hadrot

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

Voir tous ses articles

Dans la même veine

À lire aussi