Mesurer les performances avec la synchronisation du serveur
Publié: 2022-03-10Lorsque nous entreprenons tout type de travail d'optimisation des performances, l'une des toutes premières choses que nous apprenons est qu'avant de pouvoir améliorer les performances, vous devez d'abord les mesurer. Sans pouvoir mesurer la vitesse à laquelle quelque chose fonctionne, nous ne pouvons pas dire si les modifications apportées améliorent les performances, n'ont aucun effet ou même aggravent les choses.
Beaucoup d'entre nous seront habitués à travailler sur un problème de performance à un certain niveau. Cela peut être quelque chose d'aussi simple que d'essayer de comprendre pourquoi JavaScript sur votre page ne démarre pas assez tôt, ou pourquoi les images mettent trop de temps à apparaître sur un mauvais wifi d'hôtel. La réponse à ce genre de questions se trouve souvent dans un endroit très familier : les outils de développement de votre navigateur.
Au fil des ans, les outils de développement ont été améliorés pour nous aider à résoudre ces types de problèmes de performances dans le front-end de nos applications. Les navigateurs intègrent désormais même des audits de performances. Cela peut aider à détecter les problèmes frontaux, mais ces audits peuvent révéler une autre source de lenteur que nous ne pouvons pas résoudre dans le navigateur. Ce problème est la lenteur des temps de réponse du serveur.
"Temps au premier octet"
Il y a très peu d'optimisations de navigateur à faire pour améliorer une page qui est tout simplement lente à se construire sur le serveur. Ce coût est encouru entre le navigateur qui fait la demande du fichier et la réception de la réponse. L'étude de votre graphique en cascade de réseau dans les outils de développement affichera ce retard dans la catégorie "Attente (TTFB)". C'est le temps d'attente du navigateur entre l'envoi de la requête et la réception de la réponse.
En termes de performances, cela s'appelle Time to First Byte - le temps qu'il faut avant que le serveur commence à envoyer quelque chose avec lequel le navigateur peut commencer à travailler. Ce temps d'attente comprend tout ce que le serveur doit faire pour créer la page. Pour un site typique, cela peut impliquer d'acheminer la demande vers la bonne partie de l'application, d'authentifier la demande, d'effectuer plusieurs appels vers des systèmes principaux tels que des bases de données, etc. Cela peut impliquer d'exécuter du contenu via des systèmes de modèles, d'appeler des API à des services tiers, et peut-être même d'envoyer des e-mails ou de redimensionner des images. Tout travail effectué par le serveur pour répondre à une demande est écrasé dans cette attente TTFB que l'utilisateur expérimente dans son navigateur.
Alors, comment pouvons-nous réduire ce temps et commencer à livrer la page plus rapidement à l'utilisateur ? Eh bien, c'est une grande question, et la réponse dépend de votre application. C'est le travail de l'optimisation des performances elle-même. Ce que nous devons faire d'abord, c'est mesurer la performance afin que les avantages de tout changement puissent être jugés.
L'en-tête de synchronisation du serveur
Le travail de Server Timing n'est pas de vous aider à chronométrer l'activité sur votre serveur. Vous devrez faire le chronométrage vous-même en utilisant l'ensemble d'outils que votre plate-forme backend met à votre disposition. Au contraire, le but de Server Timing est de spécifier comment ces mesures peuvent être communiquées au navigateur.
La façon dont cela est fait est très simple, transparente pour l'utilisateur et a un impact minimal sur le poids de votre page. Les informations sont envoyées sous la forme d'un simple ensemble d'en-têtes de réponse HTTP.
Server-Timing: db;dur=123, tmpl;dur=56
Cet exemple communique deux points de synchronisation différents nommés db
et tmpl
. Ceux-ci ne font pas partie de la spécification - ce sont des noms que nous avons choisis, dans ce cas pour représenter respectivement certains timings de base de données et de modèle.
La propriété dur
indique le nombre de millisecondes que l'opération a pris pour se terminer. Si nous regardons la demande dans la section Réseau des outils de développement, nous pouvons voir que les horaires ont été ajoutés au tableau.
L'en-tête Server-Timing
peut prendre plusieurs métriques séparées par des virgules :
Server-Timing: metric, metric, metric
Chaque métrique peut spécifier trois propriétés possibles
- Un nom court pour la métrique (comme
db
dans notre exemple) - Une durée en millisecondes (exprimée comme
dur=123
) - Une description (exprimée sous la forme
desc="My Description"
)
Chaque propriété est séparée par un point-virgule comme délimiteur. Nous pourrions ajouter des descriptions à notre exemple comme ceci :
Server-Timing: db;dur=123;desc="Database", tmpl;dur=56;desc="Template processing"
La seule propriété requise est name
. dur
et desc
sont facultatifs et peuvent être utilisés éventuellement si nécessaire. Par exemple, si vous deviez déboguer un problème de synchronisation qui se produisait sur un serveur ou un centre de données et pas sur un autre, il peut être utile d'ajouter ces informations dans la réponse sans synchronisation associée.
Server-Timing: datacenter;desc="East coast data center", db;dur=123;desc="Database", tmpl;dur=56;desc="Template processing”
Cela apparaîtrait alors avec les horaires.
Une chose que vous remarquerez peut-être est que les barres de synchronisation ne s'affichent pas en cascade. C'est simplement parce que Server Timing n'essaie pas de communiquer la séquence de minutages, mais uniquement les métriques brutes elles-mêmes.
Mise en œuvre de la synchronisation du serveur
La mise en œuvre exacte dans votre propre application dépendra de votre situation spécifique, mais les principes sont les mêmes. Les étapes seront toujours :
- Chronométrer certaines opérations
- Rassembler les résultats de chronométrage
- Sortir l'en-tête HTTP
En pseudocode, la génération de réponse pourrait ressembler à ceci :
startTimer('db') getInfoFromDatabase() stopTimer('db') startTimer('geo') geolocatePostalAddressWithAPI('10 Downing Street, London, UK') endTimer('geo') outputHeader('Server-Timing', getTimerOutput())
Les bases de la mise en œuvre de quelque chose dans ce sens devraient être simples dans n'importe quelle langue. Une implémentation PHP très simple pourrait utiliser la fonction microtime()
pour les opérations de synchronisation et pourrait ressembler à ce qui suit.
class Timers { private $timers = []; public function startTimer($name, $description = null) { $this->timers[$name] = [ 'start' => microtime(true), 'desc' => $description, ]; } public function endTimer($name) { $this->timers[$name]['end'] = microtime(true); } public function getTimers() { $metrics = []; if (count($this->timers)) { foreach($this->timers as $name => $timer) { $timeTaken = ($timer['end'] - $timer['start']) * 1000; $output = sprintf('%s;dur=%f', $name, $timeTaken); if ($timer['desc'] != null) { $output .= sprintf(';desc="%s"', addslashes($timer['desc'])); } $metrics[] = $output; } } return implode($metrics, ', '); } }
Un script de test l'utiliserait comme ci-dessous, ici en utilisant la fonction usleep()
pour créer artificiellement un retard dans l'exécution du script afin de simuler un processus qui prend du temps à se terminer.
$Timers = new Timers(); $Timers->startTimer('db'); usleep('200000'); $Timers->endTimer('db'); $Timers->startTimer('tpl', 'Templating'); usleep('300000'); $Timers->endTimer('tpl'); $Timers->startTimer('geo', 'Geocoding'); usleep('400000'); $Timers->endTimer('geo'); header('Server-Timing: '.$Timers->getTimers());
L'exécution de ce code a généré un en-tête qui ressemblait à ceci :
Server-Timing: db;dur=201.098919, tpl;dur=301.271915;desc="Templating", geo;dur=404.520988;desc="Geocoding"
Implémentations existantes
Compte tenu de la maniabilité de Server Timing, il y a relativement peu d'implémentations que j'ai pu trouver. Le package NPM de synchronisation du serveur offre un moyen pratique d'utiliser la synchronisation du serveur à partir des projets Node.
Si vous utilisez un framework PHP basé sur un middleware, tuupola/server-timing-middleware fournit également une option pratique. J'utilise cela en production sur Notist depuis quelques mois, et je laisse toujours quelques timings de base activés si vous souhaitez voir un exemple dans la nature.
Pour la prise en charge du navigateur, le meilleur que j'ai vu se trouve dans Chrome DevTools, et c'est ce que j'ai utilisé pour les captures d'écran de cet article.
Considérations
Server Timing lui-même ajoute une surcharge très minime à la réponse HTTP renvoyée sur le réseau. L'en-tête est très minimal et peut généralement être envoyé en toute sécurité sans se soucier de cibler uniquement les utilisateurs internes. Même ainsi, il vaut la peine de garder les noms et les descriptions courts afin de ne pas ajouter de surcharge inutile.
Le travail supplémentaire que vous pourriez faire sur le serveur pour chronométrer votre page ou votre application est plus préoccupant. L'ajout de temps et de journalisation supplémentaires peut lui-même avoir un impact sur les performances, il vaut donc la peine de mettre en œuvre un moyen d'activer et de désactiver cela si nécessaire.
L'utilisation d'un en-tête de synchronisation du serveur est un excellent moyen de s'assurer que toutes les informations de synchronisation du front-end et du back-end de votre application sont accessibles au même endroit. À condition que votre application ne soit pas trop complexe, elle peut être facile à mettre en œuvre et vous pouvez être opérationnelle en très peu de temps.
Si vous souhaitez en savoir plus sur la synchronisation du serveur, vous pouvez essayer ce qui suit :
- La spécification de synchronisation du serveur W3C
- La page MDN sur Server Timing contient des exemples et des détails à jour sur la prise en charge du navigateur
- Un article intéressant de l'équipe BBC iPlayer sur leur utilisation de Server Timing.