Accélération instagram.com. 2e partie

Aujourd'hui, nous portons à votre attention une traduction du deuxième matériel d'une série dédiée à l'optimisation instagram.com. Ici, nous parlerons de l'amélioration du mécanisme d'exécution précoce des requêtes GraphQL et de l'augmentation de l'efficacité de la transmission des données HTML au client.



→ Lisez avec haleine, la première partie

Soumission des données par le serveur au client à l'aide de la technologie de téléchargement HTML progressif


Dans la première partie, nous avons expliqué comment, à l'aide de mécanismes de préchargement, commencer à exécuter des requêtes aux premiers stades du traitement des pages. C'est-à-dire avant même que le script qui initie de telles requêtes soit chargé. Compte tenu de cela, on peut noter que l'exécution de ces requêtes au stade du préchargement des matériaux signifiait toujours que leur exécution n'avait pas commencé avant le rendu de la page HTML sur le client. Et cela, à son tour, signifiait que la demande ne pouvait pas démarrer avant que le client ait envoyé une demande au serveur et que le serveur ait répondu à cette demande (ici, vous devez également ajouter le temps qu'il faut au serveur pour générer une réponse HTML au client). Dans la figure suivante, vous pouvez voir que le démarrage d'une requête GraphQL peut être assez retardé. Et c'est - étant donné que nous commençons à effectuer de telles demandes en utilisant le code situé dans la <head> HTML <head> , et que c'est l'une des premières tâches que nous résolvons à l'aide d'outils de préchargement de données.


La pré-exécution de la demande commence avec un retard notable

En théorie, le début d'une telle requête GraphQL devrait idéalement regarder le moment où une demande de chargement de la page correspondante a été envoyée au serveur. Mais comment faire en sorte que le navigateur commence à télécharger quelque chose avant même qu'il ne reçoive au moins du code HTML du serveur? La réponse est d'envoyer la ressource au navigateur à l'initiative du serveur. Il peut sembler que pour implémenter un tel mécanisme, vous aurez besoin de quelque chose comme HTTP / 2 Server Push. Mais, en fait, il existe une technologie très ancienne (qui est souvent oubliée) qui vous permet de mettre en œuvre un schéma d'interaction similaire entre le client et le serveur. Cette technologie se caractérise par la prise en charge universelle des navigateurs, pour sa mise en œuvre, il n'est pas nécessaire de se plonger dans les complexités infrastructurelles typiques de la mise en œuvre de HTTP / 2 Server Push. Facebook utilise cette technologie depuis 2010 (lire sur BigPipe ), et sur d'autres sites comme Ebay, il trouve également des applications sous diverses formes. Mais il semble que les développeurs JavaScript d'applications à page unique ignorent fondamentalement cette technologie ou ne l'utilisent tout simplement pas. Il s'agit de charger progressivement du HTML. Cette technologie est connue sous différents noms: «early flush», «head flushing», «progressive HTML». Il fonctionne grâce à une combinaison de deux mécanismes:

  • Le premier est le codage de transfert HTTP fragmenté.
  • Le second est le rendu progressif de HTML dans le navigateur.

Le mécanisme de codage de transfert par blocs est apparu dans HTTP / 1.1. Il vous permet de diviser les réponses HTTP en plusieurs petites parties qui sont transmises au navigateur en mode streaming. Le navigateur «attache» ces pièces à mesure qu'elles arrivent, formant ainsi le code de réponse complet. Bien que cette approche prévoie des changements importants dans la façon dont les pages sont formées sur le serveur, la plupart des langages et des frameworks ont la capacité de fournir des réponses similaires, divisées en parties. Les interfaces Web d'Instagram utilisent Django, nous utilisons donc l'objet StreamingHttpResponse . La raison pour laquelle l'utilisation d'un tel mécanisme peut être bénéfique est qu'il vous permet d'envoyer le contenu HTML de la page au navigateur en mode streaming lorsque des parties individuelles de la page sont prêtes, plutôt que d'attendre que le code de la page entière soit prêt. Cela signifie que nous pouvons vider le titre de la page du navigateur presque instantanément après avoir reçu la demande (d'où le terme "vidage anticipé"). La préparation des en-têtes ne nécessite pas de ressources serveur particulièrement importantes. Cela permet au navigateur de commencer à charger des scripts et des styles même lorsque le serveur est occupé à générer des données dynamiques pour le reste de la page. Voyons quel effet cette technique a. Voici à quoi ressemble un chargement de page normal.


La technologie de vidage précoce n'est pas utilisée: le chargement des ressources ne démarre pas tant que la page HTML n'est pas entièrement chargée

Mais que se passe-t-il si le serveur, à réception de la demande, transmet immédiatement le titre de la page au navigateur.


La technologie de vidage précoce est utilisée: les ressources commencent à se charger immédiatement après le vidage des balises HTML dans le navigateur

De plus, nous pouvons utiliser le mécanisme d'envoi de messages HTTP en plusieurs parties pour envoyer des données au client lorsqu'il est prêt. Dans le cas d'applications rendues sur le serveur, ces données peuvent être présentées sous forme de code HTML. Mais si nous parlons d'applications d'une seule page comme instagram.com, le serveur peut également transmettre quelque chose comme des données JSON au client. Afin de voir comment cela fonctionne, examinons l'exemple le plus simple de démarrage d'une application d'une seule page.

Tout d'abord, le balisage HTML d'origine est envoyé au navigateur contenant le code JavaScript nécessaire pour afficher la page. Après avoir analysé et exécuté ce script, une requête XHR sera exécutée, chargeant les données source nécessaires au rendu de la page.


Processus de chargement d'une page dans une situation où le navigateur demande indépendamment au serveur tout ce dont il a besoin

Ce processus implique plusieurs situations dans lesquelles le client envoie une demande au serveur et attend une réponse de celui-ci. Par conséquent, il existe des périodes où le serveur et le client sont inactifs. Au lieu d'attendre que le serveur attende la demande d'API du client, il serait plus efficace que le serveur commence à préparer la réponse d'API immédiatement après la génération du code HTML. Une fois la réponse prête, le serveur pouvait, de sa propre initiative, l'empoisonner pour le client. Cela signifierait qu'au moment où le client aurait préparé tout ce qui était nécessaire pour visualiser les données qui étaient précédemment chargées après la fin de la demande d'API, ces données auraient probablement été prêtes. Le client n'aurait pas à répondre à une demande distincte au serveur et à attendre une réponse de celui-ci.

La première étape de la mise en œuvre d'un tel schéma d'interaction client-serveur consiste à créer un cache JSON conçu pour stocker les réponses du serveur. Nous avons développé cette partie du système en utilisant un petit bloc de script intégré dans le code HTML de la page. Il joue le rôle d'un cache et contient des informations sur les requêtes qui seront ajoutées au cache par le serveur (ceci, sous une forme simplifiée, est illustré ci-dessous).

 <script type="text/javascript">  //      API,       ,  //     ,       ,    //        window.__data = {    '/my/api/path': {        waiting: [],    }  };  window.__dataLoaded = function(path, data) {    const cacheEntry = window.__data[path];    if (cacheEntry) {      cacheEntry.data = data;      for (var i = 0;i < cacheEntry.waiting.length; ++i) {        cacheEntry.waiting[i].resolve(cacheEntry.data);      }      cacheEntry.waiting = [];    }  }; </script> 

Après avoir réinitialisé le code HTML sur le navigateur, le serveur peut exécuter indépendamment les demandes d'API. Après avoir reçu des réponses à ces demandes, le serveur transfère les données JSON sur la page sous la forme d'une balise de script contenant ces données. Lorsque le navigateur reçoit et analyse un fragment similaire du code HTML de la page, cela conduit au fait que les données tomberont dans le cache JSON. La chose la plus importante ici est que le navigateur affiche progressivement la page - car il reçoit des fragments de la réponse (c'est-à-dire que les blocs de scripts finis seront exécutés à mesure qu'ils arrivent dans le navigateur). Cela signifie qu'il est tout à fait possible de générer simultanément de grandes quantités de données sur le serveur et de déposer des blocs de script sur la page dès que les données correspondantes sont prêtes. Ces scripts seront immédiatement exécutés sur le client. C'est la base du système BigPipe utilisé par Facebook. Là, de nombreux pagers indépendants sont chargés en parallèle sur le serveur et transmis au client au fur et à mesure qu'ils deviennent disponibles.

 <script type="text/javascript">  window.__dataLoaded('/my/api/path', {    // JSON- API,      ,     //    JSON-...  }); </script> 

Lorsque le script client est prêt à demander les données dont il a besoin, au lieu d'exécuter la demande XHR, il vérifie d'abord le cache JSON. Si le cache contient déjà les résultats de la requête, le script reçoit immédiatement ce dont il a besoin. Si la demande est en cours, le script attend les résultats.

 function queryAPI(path) {  const cacheEntry = window.__data[path];  if (!cacheEntry) {    //   XHR-  API    return fetch(path);  } else if (cacheEntry.data) {    //          return Promise.resolve(cacheEntry.data);  } else {    //       ,    //            //       const waiting = {};    cacheEntry.waiting.push(waiting);    return new Promise((resolve) => {      waiting.resolve = resolve;    });  } } 

Tout cela conduit au fait que le processus de chargement de la page devient le même que dans le diagramme suivant.


Processus de chargement d'une page dans une situation où le navigateur est activement impliqué dans la préparation des données pour le client

Si vous comparez cela avec la manière la plus simple de charger des pages, il s'avère que le serveur et le client peuvent désormais effectuer plus de tâches en parallèle. Cela réduit les temps d'arrêt pendant lesquels le serveur et le client s'attendent.

Cette optimisation a eu un effet très positif sur notre système. Ainsi, dans les navigateurs de bureau, le chargement des pages a commencé à se terminer 14% plus rapidement qu'auparavant. Et dans les navigateurs mobiles (en raison de retards plus longs dans les réseaux mobiles), la page a commencé à se charger 23% plus rapidement.

Chers lecteurs! Envisagez-vous d'utiliser la méthodologie pour optimiser la formation des pages Web discutée ici dans vos projets?


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


All Articles