vendredi 25 septembre 2026

À propos

Contact

Performance

Server-Sent Events plutôt que le polling admin-ajax pour du temps réel

Un tableau de bord de commandes qui interroge admin-ajax.php toutes les cinq secondes surcharge le serveur. Remplacement par des Server-Sent Events pour pousser les mises à jour.

Par Clément Hadrot • 2 avril 2025 • 5 min de lecture • Aucun commentaire
Server-Sent Events plutôt que le polling admin-ajax pour du temps réel

Un atelier de préparation de commandes pour un site de vente directe de producteurs affichait un tableau de bord interne listant les commandes à préparer, ouvert en permanence sur cinq postes de l’entrepôt. Ce tableau de bord interrogeait admin-ajax.php toutes les cinq secondes pour vérifier l’arrivée de nouvelles commandes, soit 720 requêtes par heure et par poste, 3 600 requêtes par heure cumulées sur l’ensemble de l’entrepôt, pour un nombre réel de nouvelles commandes qui dépassait rarement dix par heure en période creuse.

Le coût caché du polling systématique

Chacune de ces requêtes déclenchait un cycle complet WordPress : chargement du cœur, exécution des hooks admin-ajax, requête à la base de données pour vérifier l’existence de nouvelles commandes, avant de répondre, la plupart du temps, qu’il n’y avait rien de nouveau. Sur un serveur mutualisé avec un nombre de workers PHP-FPM limité, ces requêtes constantes en arrière-plan grignotaient une part significative de la capacité disponible, au détriment des visiteurs du site public qui partageait la même infrastructure.

Pourquoi Server-Sent Events plutôt que WebSockets

Deux options existaient pour remplacer le polling : les WebSockets, qui ouvrent un canal bidirectionnel complet, ou les Server-Sent Events (SSE), un standard plus simple qui ouvre un flux HTTP à sens unique du serveur vers le client. Le tableau de bord de l’entrepôt n’a besoin que de recevoir des notifications du serveur ; il n’a jamais besoin d’envoyer des données en retour sur ce même canal. Les SSE, plus simples à mettre en place côté serveur PHP classique et sans dépendance à un serveur applicatif dédié comme le nécessiterait souvent une solution WebSocket robuste, ont donc été retenus.

L'essentiel à retenir : Le polling multiplie les requêtes même quand rien n'a changé ; SSE pousse une mise à jour uniquement quand un événement survient ; Un flux HTTP long nécessite une configuration serveur adaptée

L’endpoint SSE côté serveur

L’endpoint garde la connexion HTTP ouverte et envoie un événement dès qu’une nouvelle commande est détectée, en interrogeant la base de données à intervalle court côté serveur (une seconde), mais sans qu’aucune requête HTTP supplémentaire ne soit nécessaire côté client entre deux événements.

add_action( 'wp_ajax_flux_commandes', 'entrepot_flux_commandes_sse' );

function entrepot_flux_commandes_sse() {
    header( 'Content-Type: text/event-stream' );
    header( 'Cache-Control: no-cache' );
    header( 'X-Accel-Buffering: no' ); // désactive la mise en tampon Nginx

    $derniere_id_vue = isset( $_GET['depuis'] ) ? absint( $_GET['depuis'] ) : 0;
    $limite_temps = time() + 60; // on referme la connexion après 60 s, le client la rouvre

    while ( time() < $limite_temps ) {
        $nouvelles = wc_get_orders( [
            'status'  => 'processing',
            'orderby' => 'ID',
            'order'   => 'ASC',
            'exclude' => [],
            'limit'   => 10,
        ] );

        foreach ( $nouvelles as $commande ) {
            if ( $commande->get_id() > $derniere_id_vue ) {
                echo "data: " . wp_json_encode( [
                    'id'     => $commande->get_id(),
                    'client' => $commande->get_formatted_billing_full_name(),
                ] ) . "\n\n";
                $derniere_id_vue = $commande->get_id();
                flush();
            }
        }

        sleep( 1 );
    }

    exit;
}

Le client JavaScript

Le navigateur ouvre une connexion via l’API native EventSource, qui gère nativement la reconnexion automatique en cas de coupure, sans qu’aucune bibliothèque tierce ne soit nécessaire.

let derniereId = 0;

function ouvrirFlux() {
  const source = new EventSource(`/wp-admin/admin-ajax.php?action=flux_commandes&depuis;=${derniereId}`);

  source.onmessage = function (event) {
    const commande = JSON.parse(event.data);
    derniereId = commande.id;
    ajouterLigneCommande(commande);
    jouerSonNotification();
  };

  source.onerror = function () {
    source.close();
    setTimeout(ouvrirFlux, 2000); // reconnexion après une coupure
  };
}

ouvrirFlux();

Configuration serveur à ne pas oublier

Un flux SSE nécessite que le serveur web ne mette pas la réponse en tampon avant de l’envoyer au client, sous peine de recevoir tous les événements d’un coup à la fermeture de la connexion plutôt qu’au fil de l’eau. Sur Nginx, l’en-tête X-Accel-Buffering: no désactive ce comportement pour la route concernée ; sur Apache, il faut vérifier qu’aucun module de compression ne bufferise la sortie de la même façon.

Résultat mesuré

Le volume de requêtes HTTP par poste est passé de 720 par heure (polling toutes les cinq secondes) à environ 4 par heure (une ouverture de connexion SSE toutes les minutes, la connexion étant volontairement refermée et rouverte pour éviter de garder un worker PHP-FPM occupé indéfiniment). La détection d’une nouvelle commande, auparavant retardée jusqu’à cinq secondes par le cycle de polling, est devenue quasiment instantanée.

  • Le polling coûte cher même quand il ne trouve rien de nouveau à signaler.
  • Les SSE conviennent aux flux à sens unique serveur vers client, sans la complexité des WebSockets.
  • La configuration du buffering serveur est la principale source de bugs silencieux avec les SSE.

Interroger un serveur toutes les cinq secondes pour lui demander s’il s’est passé quelque chose revient à téléphoner à quelqu’un toutes les cinq minutes pour lui demander s’il a du nouveau à annoncer.

Hors périmètre

Cet article ne traite pas les WebSockets, plus adaptés à un besoin bidirectionnel, ni Action Scheduler, qui répond à un besoin différent de traitement en arrière-plan et non de notification en temps réel vers un navigateur ouvert.

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