# Un agenda pour 300 praticiens : dimensionner une extension de planning médical

> Un groupement de santé de 300 praticiens fait exploser les limites d'un module d'agenda pensé pour un cabinet isolé. Comment repenser stockage et requêtes sans réécrire toute la logique métier.

- Auteur : Clément Hadrot
- Publié le : 2026-09-17
- Mis à jour le : 2026-09-17
- Catégorie : Extensions
- URL : https://wpmoderne.dev.wordpress-developpement.fr/extensions/extension-cabinet-medical-300-praticiens-agenda/

## L’essentiel

- Une table de créneaux plutôt que des métadonnées de rendez-vous
- Index composite sur praticien, date et statut
- Pagination par curseur plutôt que par numéro de page

Une requête qui met douze secondes à afficher l'agenda du jour n'est jamais un problème d'affichage : c'est un problème de modèle de données qui n'a pas grandi avec l'usage. C'est exactement ce qu'a révélé le passage d'un module d'agenda conçu pour un cabinet de deux ou trois praticiens à un déploiement pour un groupement de santé régional comptant trois cents praticiens répartis sur une douzaine de sites.

Le module d'origine stockait chaque rendez-vous comme un article personnalisé, avec le praticien, la date et le statut rangés en métadonnées de post. Ce choix, raisonnable à petite échelle, devient le principal goulot d'étranglement dès que le volume de rendez-vous dépasse quelques dizaines de milliers de lignes dans `wp_postmeta`. Cet article ne couvre pas la prise de rendez-vous elle-même, déjà traitée par ailleurs : il se concentre sur ce qui doit changer pour que l'affichage de l'agenda reste rapide à cette échelle.

## Pourquoi les métadonnées de post ne suivent plus

Rechercher les rendez-vous d'un praticien donné pour une journée précise, quand la date et l'identifiant du praticien sont stockés en métadonnées, oblige `WP_Query` à générer une jointure sur `wp_postmeta` pour chaque critère. Avec trois cents praticiens et plusieurs milliers de créneaux par semaine, la table de métadonnées grossit bien plus vite que la table de posts elle-même, et aucun index natif ne couvre efficacement une recherche combinée praticien + date + statut.

## Passer à une table personnalisée de créneaux

> L'essentiel à retenir : Une table de créneaux plutôt que des métadonnées de rendez-vous ; Index composite sur praticien, date et statut ; Pagination par curseur plutôt que par numéro de page

La solution retenue déplace le stockage des créneaux vers une table personnalisée dédiée, créée via `dbDelta()`, avec des colonnes typées plutôt que des métadonnées génériques :

```
CREATE TABLE {$wpdb->prefix}gs_creneaux (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    praticien_id BIGINT UNSIGNED NOT NULL,
    site_id BIGINT UNSIGNED NOT NULL,
    debut DATETIME NOT NULL,
    fin DATETIME NOT NULL,
    statut VARCHAR(20) NOT NULL DEFAULT 'disponible',
    patient_id BIGINT UNSIGNED NULL,
    KEY praticien_date_statut (praticien_id, debut, statut),
    KEY site_date (site_id, debut)
) {$charset_collate};
```

L'index composite `praticien_date_statut` est celui qui change tout : une requête filtrant sur le praticien, une plage de dates et un statut de créneau s'exécute désormais en quelques millisecondes, contre plusieurs secondes avec l'ancien schéma basé sur les métadonnées.

## Pagination par curseur pour l'écran d'administration

L'écran listant l'ensemble des créneaux d'un site sur un mois utilisait une pagination classique par numéro de page, avec un `LIMIT` et un `OFFSET` qui grandit. Sur un volume de plusieurs dizaines de milliers de lignes, un `OFFSET` élevé oblige MySQL à parcourir puis ignorer toutes les lignes précédentes, ce qui ralentit fortement les dernières pages. La bascule vers une pagination par curseur — en gardant le dernier `id` ou le dernier horodatage affiché comme point de reprise — supprime ce ralentissement quel que soit le numéro de page consulté :

- Ancienne requête : `SELECT * FROM gs_creneaux ORDER BY debut LIMIT 20 OFFSET 8000`.
- Nouvelle requête : `SELECT * FROM gs_creneaux WHERE debut > '2026-09-17 09:00:00' ORDER BY debut LIMIT 20`.

## Ce que la migration a coûté en réécriture

Basculer d'un stockage en métadonnées vers une table personnalisée n'est jamais gratuit : chaque point du code qui utilisait `get_post_meta()` pour lire un créneau a dû être réécrit avec des requêtes préparées via `$wpdb`, et un script de migration ponctuel a converti l'historique existant, praticien par praticien, avec vérification du nombre de lignes migrées avant suppression des anciennes métadonnées. Cette phase a représenté plus de la moitié du temps de développement, loin devant la conception du nouveau schéma.

> Une migration de métadonnées vers une table personnalisée ne se juge pas au nombre de lignes de SQL écrites, mais au nombre d'endroits dans le code qui supposaient, sans le dire, que `get_post_meta()` resterait toujours assez rapide.

## Résultat mesuré après migration

L'affichage de l'agenda hebdomadaire d'un praticien est passé d'un temps de réponse moyen de plusieurs secondes à moins de deux cents millisecondes sur le même volume de données. L'écran d'administration listant l'ensemble des créneaux du groupement, auparavant inutilisable au-delà de la dixième page, reste désormais constant en temps de réponse quel que soit le nombre de créneaux déjà parcourus.

## Notre verdict

À l'échelle d'un cabinet isolé, stocker un rendez-vous en métadonnées de post reste parfaitement défendable. À l'échelle d'un groupement de trois cents praticiens, c'est une table personnalisée avec des index composites pensés pour les requêtes réellement exécutées qui fait la différence entre un agenda utilisable et un agenda qu'on évite d'ouvrir un lundi matin chargé.
