POST /wp-admin/admin-ajax.php action=hubspot_sync_contact 200 OK — cette ligne revenait dans les journaux d’accès toutes les trente secondes, jour et nuit, sur l’intranet d’un réseau associatif national. Personne n’avait remarqué le problème avant que le temps de réponse de l’espace adhérents ne commence à dépasser les quatre secondes en pleine journée, sans lien apparent avec le trafic public du site.
L’intranet servait à la fois de vitrine publique légère et d’espace de gestion pour une quarantaine de salariés répartis dans les antennes régionales. Chacun avait HubSpot ouvert dans un onglet, et un plugin maison synchronisait en continu les fiches contact entre HubSpot et une table personnalisée de WordPress, pour alimenter un tableau de bord interne. Cette synchronisation passait systématiquement par admin-ajax.php, appelée en JavaScript depuis chaque poste ouvert.
Le calcul qui a tout expliqué
Une synchronisation toutes les trente secondes, multipliée par une quarantaine de postes ouverts en simultané sur une journée de travail, cela donne un ordre de grandeur simple à poser sur le papier mais facile à rater en développement : environ 4 800 requêtes par heure vers admin-ajax.php, chacune déclenchant un appel sortant vers l’API HubSpot et une écriture en base côté WordPress. Le plugin avait été testé avec deux ou trois postes ouverts en recette, jamais à l’échelle réelle des salariés connectés.
Chaque requête exécutait une fonction accrochée à wp_ajax_hubspot_sync_contact, qui interrogeait l’API HubSpot, comparait les champs modifiés, puis mettait à jour une table wp_hubspot_contacts via $wpdb->update(). Rien d’anormal dans le code pris isolément ; le problème venait entièrement de la fréquence d’appel, jamais remise en question depuis la mise en place initiale.

Le diagnostic posé avec Query Monitor et les journaux serveur
Query Monitor a d’abord montré que la majorité du temps CPU du serveur PHP-FPM n’était pas consommée par les pages publiques mais par des requêtes AJAX authentifiées, invisibles pour un audit classique orienté visiteurs anonymes. Croiser cette information avec les journaux nginx a confirmé le rythme : une requête admin-ajax par poste et par demi-minute, en continu sur les heures ouvrées.
- Le cache de page ne servait à rien ici : les requêtes AJAX authentifiées ne passent jamais par le cache de page classique.
- Chaque appel HubSpot ajoutait une latence réseau sortante de 150 à 300 ms, bloquant le worker PHP-FPM pendant tout ce temps.
- Le pool PHP-FPM, dimensionné pour le trafic public, se retrouvait saturé par ce trafic interne invisible dans les tableaux de bord habituels.
Pourquoi le temps réel n’était pas justifié
En creusant l’usage réel du tableau de bord interne, la fréquence de trente secondes ne correspondait à aucun besoin métier documenté : personne ne consultait les fiches contact assez souvent pour avoir besoin d’une mise à jour quasi instantanée. La demande initiale de « temps réel » venait d’une habitude reprise d’un autre projet, jamais reconfirmée avec les utilisateurs finaux de cet intranet.
Avant d’optimiser une synchronisation, nous demandons toujours à quelle fréquence l’information est réellement consultée. La réponse est presque toujours plus lente que ce que le code fait tourner.
Le correctif : cadencement horaire via WP-Cron
La solution retenue a remplacé l’appel JavaScript continu par une tâche planifiée, exécutée une fois par heure via wp_schedule_event, qui synchronise l’ensemble des contacts modifiés depuis le dernier passage grâce au filtre lastmodifieddate de l’API HubSpot :
add_action( 'init', function () {
if ( ! wp_next_scheduled( 'reseau_hubspot_sync_horaire' ) ) {
wp_schedule_event( time(), 'hourly', 'reseau_hubspot_sync_horaire' );
}
} );
add_action( 'reseau_hubspot_sync_horaire', function () {
$depuis = get_option( 'reseau_hubspot_derniere_sync', 0 );
$contacts = reseau_recuperer_contacts_hubspot_modifies( $depuis );
foreach ( $contacts as $contact ) {
reseau_mettre_a_jour_fiche_locale( $contact );
}
update_option( 'reseau_hubspot_derniere_sync', time() );
} );
Le tableau de bord interne affiche désormais un horodatage « dernière synchronisation », que les salariés ont accepté sans difficulté une fois expliqué que la fraîcheur horaire suffisait largement à leur usage quotidien.
Résultat mesuré après une semaine
Les requêtes vers admin-ajax.php sont tombées de 4 800 à environ 40 par heure (une par exécution de la tâche planifiée, répartie sur les antennes concernées), et le temps de réponse moyen de l’espace adhérents est repassé sous la seconde en journée. Le pool PHP-FPM, resté au même dimensionnement, n’a plus jamais montré de saturation liée à ce trafic interne.
En résumé
Le vrai coût de cette synchronisation n’était pas dans son code, mais dans son rythme, jamais confronté à l’usage réel. Avant de blâmer une API tierce ou un plugin, il vaut la peine de compter, noir sur blanc, le nombre de requêtes générées par un mécanisme « temps réel » multiplié par le nombre de postes qui l’exécutent réellement.