Service Workers in Slack Client: Accélération de téléchargement et mode hors ligne

Le matériel, dont nous publions la traduction aujourd'hui, est consacré à l'histoire de l'optimisation de la nouvelle version du client de bureau Slack , dont l'une des principales caractéristiques était l'accélération du chargement.



Contexte


Au début des travaux sur la nouvelle version du client de bureau Slack, un prototype a été créé, appelé «speedy boots». Le but de ce prototype, comme vous pouvez le deviner, était d'accélérer le téléchargement autant que possible. En utilisant le fichier HTML du cache CDN, les données de stockage Redux stockées à l'avance et le technicien de service, nous avons pu charger la version allégée du client en moins d'une seconde (à ce moment-là, le temps de téléchargement habituel pour les utilisateurs avec 1-2 espaces de travail était d'environ 5 secondes ) Le travailleur de service a été au centre de cette accélération. En outre, il a ouvert la voie à des opportunités que nous avons souvent demandé aux utilisateurs de Slack de mettre en œuvre. Nous parlons du mode hors ligne du client. Le prototype nous a permis de voir littéralement d'un œil ce dont le client refait peut être capable. Sur la base des technologies ci-dessus, nous avons commencé à traiter le client Slack, imaginant approximativement le résultat et nous concentrant sur l'accélération du chargement et la mise en œuvre du mode de fonctionnement du programme hors ligne. Parlons du fonctionnement du cœur du client mis à jour.

Qu'est-ce qu'un travailleur de service?


Service Worker ( Service Worker ) est, en fait, un puissant objet proxy pour les requêtes réseau, qui permet au développeur, à l'aide d'une petite quantité de code JavaScript, de contrôler la façon dont le navigateur traite les requêtes HTTP individuelles. Les techniciens de service prennent en charge une API de mise en cache avancée et flexible, conçue pour que les objets Request soient utilisés comme clés et les objets Response comme valeurs. Les travailleurs de service, comme les travailleurs Web, sont exécutés dans leurs propres processus, en dehors du thread d'exécution de code JavaScript principal de n'importe quelle fenêtre de navigateur.

Service Worker est un adepte d' Application Cache , désormais obsolète. Il s'agissait d'un ensemble d'API représentées par l'interface AppCache , qui était utilisée pour créer des sites qui implémentent des fonctionnalités hors ligne. Lorsque vous travaillez avec AppCache , un fichier manifeste statique a été utilisé qui décrit les fichiers que le développeur souhaite mettre en cache pour une utilisation hors ligne. Dans l'ensemble, les fonctionnalités d' AppCache limitées à cela. Ce mécanisme était simple, mais pas flexible, ce qui ne donnait pas au développeur un contrôle spécial sur le cache. Au W3C, cela a été pris en compte lors du développement de la spécification Service Worker . En conséquence, les techniciens de maintenance permettent au développeur de gérer de nombreux détails concernant chaque session d'interaction réseau effectuée par une application Web ou un site Web.

Lorsque nous avons commencé à travailler avec cette technologie, Chrome était le seul navigateur à la prendre en charge, mais nous savions qu'il n'y avait pas beaucoup de temps pour attendre un large soutien pour les travailleurs du service. Maintenant, cette technologie est partout , elle est prise en charge par tous les principaux navigateurs.

Comment nous utilisons les travailleurs des services


Lorsqu'un utilisateur lance un nouveau client Slack pour la première fois, nous téléchargeons un ensemble complet de ressources (HTML, JavaScript, CSS, polices et sons) et les plaçons dans le cache du technicien de service. De plus, nous créons une copie du magasin Redux situé en mémoire et écrivons cette copie dans la base de données IndexedDB. Lors du prochain lancement du programme, nous vérifions la présence de caches correspondants. S'ils le sont, nous les utilisons lors du téléchargement de l'application. Si l'utilisateur est connecté à Internet, nous téléchargeons les dernières données après le lancement de l'application. Sinon, le client reste opérationnel.

Afin de distinguer les deux options ci-dessus pour charger le client - nous leur avons donné les noms: téléchargement chaud (chaud) et froid (froid). Le démarrage à froid d'un client se produit le plus souvent lorsque l'utilisateur lance le programme pour la toute première fois. Dans cette situation, aucune ressource en cache ou donnée Redux n'est stockée. Avec un démarrage à chaud, nous avons tout ce dont vous avez besoin pour exécuter le client Slack sur l'ordinateur de l'utilisateur. Veuillez noter que la plupart des ressources binaires (images, PDF, vidéos, etc.) sont traitées à l'aide du cache du navigateur (ces ressources sont contrôlées par des en-têtes de mise en cache classiques). Un technicien ne doit pas les traiter de manière spéciale afin que nous puissions travailler avec eux hors ligne.


Le choix entre chargement chaud et froid

Cycle de vie des travailleurs de service


Les travailleurs des services peuvent gérer trois événements du cycle de vie. Ce sont installer , récupérer et activer . Ci-dessous, nous parlerons de la façon dont nous répondons à chacun de ces événements, mais nous devons d'abord parler du téléchargement et de l'enregistrement du travailleur lui-même. Son cycle de vie dépend de la façon dont le navigateur gère les mises à jour des fichiers du service worker. Voici ce que vous pouvez lire à ce sujet sur MDN : «L'installation est terminée si le fichier téléchargé est reconnu comme nouveau. Cela peut être soit un fichier qui diffère de celui existant (la différence dans les fichiers est déterminée en les comparant par octet), ou un fichier de service worker qui a été rencontré pour la première fois par le navigateur sur la page en cours de traitement. »

Chaque fois que nous mettons à jour le fichier JavaScript, CSS ou HTML correspondant, il passe par le plugin Webpack personnalisé, qui crée un manifeste avec une description des fichiers correspondants avec des hachages uniques ( voici un exemple abrégé d'un fichier manifeste). Ce manifeste est incorporé dans le code de l'employé de service, ce qui entraîne la mise à jour de l'employé de service au prochain démarrage. De plus, cela se fait même lorsque la mise en œuvre du travailleur de service ne change pas.

▍Événement


Chaque fois qu'un technicien de service est mis à jour, nous obtenons un événement d' install . En réponse à cela, nous parcourons les fichiers, dont les descriptions sont contenues dans le manifeste intégré dans le service worker, chargeons chacun d'eux et les plaçons dans le bloc de cache correspondant. Le stockage de fichiers est organisé à l'aide de la nouvelle API de cache , qui fait partie de la spécification Service Worker. Cette API stocke des objets Response utilisant des objets Request comme clés. En conséquence, il s'avère que le stockage est incroyablement simple. Cela va bien avec la façon dont les événements de service worker reçoivent les demandes et renvoient les réponses.

Les clés des blocs de cache sont attribuées en fonction du temps de déploiement de la solution. L'horodatage est incorporé dans le code HTML, par conséquent, il peut être envoyé, dans le cadre du nom de fichier, dans la demande de téléchargement de chaque ressource. La mise en cache distincte des ressources de chaque déploiement est importante afin d'éviter de partager des ressources incompatibles. Grâce à cela, nous pouvons être sûrs que le fichier HTML initialement téléchargé ne téléchargera que des ressources compatibles, et cela est vrai à la fois lorsqu'ils sont téléchargés sur le réseau et lorsqu'ils sont téléchargés à partir du cache.

▍Event fetch


Une fois le technicien de service enregistré, il commencera à traiter toutes les demandes réseau appartenant à la même source. Un développeur ne peut pas faire en sorte que certaines demandes soient traitées par un technicien de service, tandis que d'autres ne le sont pas. Mais le développeur a un contrôle total sur ce qui doit être fait exactement avec les demandes reçues par le technicien de service.

Lors du traitement d'une demande, nous l'examinons d'abord. Si ce qui est demandé est présent dans le manifeste et se trouve dans le cache, nous renvoyons la réponse à la demande en prenant les données du cache. Si le cache n'a pas ce dont vous avez besoin, nous renvoyons une demande réseau réelle qui accède à la ressource réseau réelle comme si le technicien de service n'était pas du tout impliqué dans ce processus. Voici une version simplifiée de notre gestionnaire d'événements fetch :

 self.addEventListener('fetch', (e) => {  if (assetManifest.includes(e.request.url) {    e.respondWith(      caches        .open(cacheKey)        .then(cache => cache.match(e.request))        .then(response => {          if (response) return response;          return fetch(e.request);        });    );  } else {    e.respondWith(fetch(e.request));  } }); 

En réalité, un tel code contient beaucoup plus de logique spécifique à Slack, mais le cœur de notre gestionnaire est aussi simple que dans cet exemple.


Lors de l'analyse des interactions réseau, les réponses renvoyées par le technicien de service peuvent être reconnues par la marque ServiceWorker dans la colonne indiquant la quantité de données

▍Event activer


L'événement activate est activate après l'installation réussie d'un technicien de service nouveau ou mis à jour. Nous l'utilisons pour analyser les ressources mises en cache et invalider les blocs de cache datant de plus de 7 jours. Il s'agit d'une bonne pratique de maintenir le système en ordre et, en outre, cela vous permet de vous assurer que des ressources trop anciennes ne sont pas utilisées lors du chargement du client.

Code client en retard sur la dernière version


Vous avez peut-être remarqué que notre implémentation implique que toute personne qui démarre le client Slack après le tout premier démarrage du client ne recevra pas les ressources les plus récentes mais mises en cache chargées lors de l'enregistrement précédent du technicien de service. Dans l'implémentation client d'origine, nous avons essayé de mettre à jour le service worker après chaque téléchargement. Cependant, un utilisateur typique de Slack peut, par exemple, télécharger un programme une seule fois par jour, le matin. Cela peut conduire au fait qu'il travaillera constamment avec un client dont le code pour toute la journée est en retard sur la dernière version (nous publions de nouvelles versions plusieurs fois par jour).

Contrairement à un site Web typique, qui, lors de sa visite, part rapidement, le client Slack sur l'ordinateur de l'utilisateur est ouvert pendant des heures et est en état ouvert. En conséquence, notre code a une durée de vie assez longue, ce qui nous oblige à utiliser des approches spéciales pour maintenir sa pertinence.

Dans le même temps, nous nous efforçons de veiller à ce que les utilisateurs travaillent avec les dernières versions du code, afin qu'ils reçoivent les dernières fonctionnalités, corrections de bogues et améliorations des performances. Peu de temps après avoir publié un nouveau client, nous avons implémenté un mécanisme qui nous permet de réduire l'écart entre ce avec quoi les utilisateurs travaillent et ce que nous avons publié. Si, après la dernière mise à jour, une nouvelle version du système a été déployée, nous chargeons de nouvelles ressources qui seront utilisées au prochain démarrage du client. Si rien de nouveau ne peut être trouvé, alors rien n'est chargé. Une fois cette modification apportée au client, la durée de vie moyenne des ressources avec lesquelles le client a été chargé a été divisée par deux.


De nouvelles versions du code sont téléchargées régulièrement, mais lors du téléchargement du programme, seule la dernière version est utilisée

Synchronisation des nouveaux indicateurs de fonctionnalité


Avec l'aide de drapeaux de nouvelles fonctionnalités (Feature Flags), nous marquons dans la base de code sur laquelle le travail n'est pas encore terminé. Cela nous permet d'inclure de nouvelles fonctionnalités dans le code avant leurs versions publiques. Cette approche réduit le risque d'erreurs de production du fait que les nouvelles fonctionnalités peuvent être testées librement avec le reste de l'application, ceci bien avant que le travail sur celles-ci ne soit terminé.

Les nouvelles fonctionnalités de Slack sont généralement publiées lorsqu'elles modifient les API correspondantes. Avant de commencer à utiliser les techniciens de maintenance, nous avions la garantie que les nouvelles fonctionnalités et modifications de l'API seraient toujours synchronisées. Mais après avoir commencé à utiliser le cache, qui ne contient peut-être pas la dernière version du code, il s'est avéré que le client peut se trouver dans une situation où le code n'est pas synchronisé avec les capacités du backend. Afin de faire face à ce problème, nous mettons en cache non seulement les ressources, mais également certaines réponses API.

Le fait que les employés de service traitent absolument toutes les demandes réseau a simplifié la solution. À chaque mise à jour de l'agent de service, nous exécutons, entre autres, les demandes d'API, en mettant les réponses en cache dans le même bloc de cache que les ressources correspondantes. Cela relie les capacités et les fonctions expérimentales aux bonnes ressources - potentiellement obsolètes, mais garanties d'être cohérentes les unes avec les autres.

Ceci, en fait, n'est que la pointe de l'iceberg des opportunités disponibles pour le développeur grâce aux travailleurs de service. Un problème qui n'a pas pu être résolu à l'aide du mécanisme AppCache , ou qui nécessiterait la AppCache mécanismes client et serveur, peut être facilement et naturellement résolu en utilisant des travailleurs de service et l'API Cache.

Résumé


Le technicien de service a accéléré le chargement du client Slack en organisant le stockage local des ressources prêtes à être utilisées au prochain démarrage du client. Le réseau - principale source de retards et d'ambiguïtés que nos utilisateurs pourraient rencontrer, n'a désormais pratiquement aucun effet sur la situation. Pour ainsi dire, nous l'avons retiré de l'équation. Et si vous pouvez supprimer le réseau de l'équation, il s'avère que vous pouvez implémenter des fonctionnalités hors ligne dans le projet. Notre prise en charge du mode hors ligne est très simple en ce moment. L'utilisateur peut télécharger le client et lire les messages des conversations téléchargées. Le système prépare en même temps les marques de synchronisation sur les messages lus. Mais maintenant, nous avons une base pour la future mise en œuvre de mécanismes plus avancés.

Après plusieurs mois de développement, d'expérimentation et d'optimisation, nous avons beaucoup appris sur la façon dont les travailleurs des services travaillent dans la pratique. De plus, il s'est avéré que cette technologie est bien adaptée aux projets de grande envergure. En moins d'un mois après la publication publique d'un client avec un technicien de service, nous traitons avec succès des dizaines de millions de demandes par jour de millions de techniciens de service installés. Cela a conduit à une réduction d'environ 50% du temps de chargement des nouveaux clients par rapport aux anciens et au fait que le chargement à chaud est environ 25% plus rapide que le froid.


De gauche à droite: chargement d'un ancien client, chargement à froid d'un nouveau client, chargement à chaud d'un nouveau client (plus l'indicateur est bas, mieux c'est)

Chers lecteurs! Utilisez-vous des travailleurs de service dans vos projets?


Source: https://habr.com/ru/post/fr468409/


All Articles