Beschleunigung instagram.com. Teil 2

Heute machen wir Sie auf eine Übersetzung des zweiten Materials aus einer Reihe aufmerksam, die der Optimierung von instagram.com gewidmet ist. Hier werden wir über die Verbesserung des Mechanismus für die frühzeitige Ausführung von GraphQL-Abfragen und die Steigerung der Effizienz der Übertragung von HTML-Daten an den Client sprechen.



→ Lesen Sie mit angehaltenem Atem den ersten Teil

Vom Server initiierte Datenübermittlung an den Client mithilfe der progressiven HTML-Download-Technologie


Im ersten Teil haben wir darüber gesprochen, wie mithilfe von Vorlademechanismen Abfragen in den frühen Phasen der Seitenverarbeitung ausgeführt werden können. Das heißt - noch bevor das Skript, das solche Anforderungen initiiert, geladen wird. In Anbetracht dessen kann angemerkt werden, dass die Ausführung dieser Anforderungen in der Phase des Vorladens von Materialien immer noch bedeutete, dass ihre Ausführung nicht vor dem Rendern der HTML-Seite auf dem Client begann. Dies bedeutete wiederum, dass die Anforderung nicht gestartet werden konnte, bevor der Client dem Server eine Anforderung gesendet und der Server auf diese Anforderung geantwortet hat (hier müssen Sie auch die Zeit hinzufügen, die der Server benötigt, um eine HTML-Antwort an den Client zu generieren). In der folgenden Abbildung sehen Sie, dass der Start einer GraphQL-Abfrage sehr verzögert sein kann. Und dies ist - vorausgesetzt, wir beginnen mit der Ausführung solcher Anforderungen mithilfe des Codes im HTML-Tag <head> und dies ist eine der ersten Aufgaben, die wir mithilfe von Tools zum Vorladen von Daten lösen.


Die vorläufige Ausführung der Anfrage beginnt mit einer spürbaren Verzögerung

Theoretisch würde der Start einer solchen GraphQL-Abfrage idealerweise in dem Moment erfolgen, in dem eine Anforderung zum Laden der entsprechenden Seite an den Server gesendet wurde. Aber wie kann man den Browser dazu bringen, etwas herunterzuladen, bevor er mindestens HTML-Code vom Server erhält? Die Antwort besteht darin, die Ressource auf Initiative des Servers an den Browser zu senden. Es scheint, dass Sie zur Implementierung eines solchen Mechanismus so etwas wie HTTP / 2 Server Push benötigen. Tatsächlich gibt es jedoch eine sehr alte Technologie (die oft vergessen wird), mit der Sie ein ähnliches Interaktionsschema zwischen Client und Server implementieren können. Diese Technologie zeichnet sich durch universelle Browserunterstützung aus. Für ihre Implementierung ist es nicht erforderlich, sich mit den infrastrukturellen Komplexitäten zu befassen, die für die Implementierung von HTTP / 2 Server Push typisch sind. Facebook nutzt diese Technologie seit 2010 (lesen Sie mehr über BigPipe ) und findet auf anderen Websites wie Ebay auch Anwendung in verschiedenen Formen. Es scheint jedoch, dass JavaScript-Entwickler von Anwendungen mit nur einer Seite diese Technologie entweder ignorieren oder einfach nicht verwenden. Es geht darum, HTML schrittweise zu laden. Diese Technologie ist unter verschiedenen Namen bekannt: "Early Flush", "Head Flushing", "Progressive HTML". Es funktioniert dank einer Kombination von zwei Mechanismen:

  • Die erste ist die HTTP-Chunked-Transfer-Codierung.
  • Das zweite ist das progressive Rendern von HTML im Browser.

Der Chunked-Transfer-Codierungsmechanismus wurde in HTTP / 1.1 angezeigt. Sie können HTTP-Antworten in viele kleine Teile aufteilen, die im Streaming-Modus an den Browser übertragen werden. Der Browser „befestigt“ diese Teile bei ihrem Eintreffen und bildet daraus den vollständigen Antwortcode. Obwohl dieser Ansatz erhebliche Änderungen in der Art und Weise vorsieht, wie Seiten auf dem Server erstellt werden, können die meisten Sprachen und Frameworks ähnliche Antworten liefern, die in Teile unterteilt sind. Instagram-Web-Frontends verwenden Django, daher verwenden wir das StreamingHttpResponse- Objekt. Der Grund, warum die Verwendung eines solchen Mechanismus von Vorteil sein kann, besteht darin, dass Sie den HTML-Inhalt der Seite im Streaming-Modus an den Browser senden können, wenn einzelne Teile der Seite bereit sind, anstatt darauf zu warten, dass der vollständige Seitencode bereit ist. Dies bedeutet, dass wir den Seitentitel des Browsers fast sofort nach Erhalt der Anfrage löschen können (daher der Begriff "Early Flush"). Die Header-Vorbereitung erfordert keine besonders großen Serverressourcen. Auf diese Weise kann der Browser mit dem Laden von Skripten und Stilen beginnen, selbst wenn der Server dynamische Daten für den Rest der Seite generiert. Werfen wir einen Blick darauf, welchen Effekt diese Technik hat. So sieht ein normales Laden von Seiten aus.


Die Early-Flush-Technologie wird nicht verwendet: Das Laden von Ressourcen beginnt erst, wenn der Seiten-HTML-Code vollständig geladen ist

Was passiert jedoch, wenn der Server nach Erhalt der Anfrage den Seitentitel sofort an den Browser weitergibt?


Early Flush-Technologie wird verwendet: Ressourcen werden sofort geladen, nachdem HTML-Tags in den Browser kopiert wurden

Darüber hinaus können wir den Mechanismus zum Senden von HTTP-Nachrichten in Teilen verwenden, um Daten an den Client zu senden, sobald diese bereit sind. Bei Anwendungen, die auf dem Server gerendert werden, können diese Daten in Form von HTML-Code dargestellt werden. Wenn es sich jedoch um einseitige Anwendungen wie instagram.com handelt, kann der Server auch JSON-Daten an den Client übertragen. Um zu sehen, wie dies funktioniert, schauen wir uns das einfachste Beispiel für das Starten einer einseitigen Anwendung an.

Zunächst wird das ursprüngliche HTML-Markup an den Browser gesendet, der den zum Rendern der Seite erforderlichen JavaScript-Code enthält. Nach dem Parsen und Ausführen dieses Skripts wird eine XHR-Anforderung ausgeführt, in der die zum Rendern der Seite erforderlichen Quelldaten geladen werden.


Der Vorgang des Ladens einer Seite in einer Situation, in der der Browser unabhängig vom Server alles anfordert, was er benötigt

Dieser Prozess umfasst mehrere Situationen, in denen der Client eine Anforderung an den Server sendet und auf eine Antwort von diesem wartet. Infolgedessen gibt es Zeiträume, in denen sowohl der Server als auch der Client inaktiv sind. Anstatt darauf zu warten, dass der Server auf die API-Anforderung vom Client wartet, wäre es effizienter, wenn der Server unmittelbar nach der Generierung des HTML-Codes mit der Vorbereitung der API-Antwort beginnen würde. Nachdem die Antwort fertig war, konnte der Server sie von sich aus für den Client vergiften. Dies würde bedeuten, dass zu dem Zeitpunkt, zu dem der Client alles vorbereitet hat, was zur Visualisierung der Daten erforderlich ist, die zuvor nach Abschluss der API-Anforderung geladen wurden, diese Daten höchstwahrscheinlich bereit waren. Der Client müsste keine separate Anforderung an den Server erfüllen und auf eine Antwort von ihm warten.

Der erste Schritt bei der Implementierung eines solchen Client-Server-Interaktionsschemas besteht darin, einen JSON-Cache zum Speichern von Serverantworten zu erstellen. Wir haben diesen Teil des Systems mithilfe eines kleinen Skriptblocks entwickelt, der in den HTML-Code der Seite eingebettet ist. Es spielt die Rolle eines Caches und enthält Informationen zu Anforderungen, die vom Server zum Cache hinzugefügt werden (dies wird in vereinfachter Form unten gezeigt).

 <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> 

Nach dem Zurücksetzen des HTML-Codes auf den Browser kann der Server API-Anforderungen unabhängig ausführen. Nach Erhalt der Antworten auf diese Anforderungen speichert der Server JSON-Daten in Form eines Skript-Tags, das diese Daten enthält, auf der Seite. Wenn der Browser ein ähnliches Fragment des HTML-Codes der Seite empfängt und analysiert, führt dies dazu, dass die Daten in den JSON-Cache fallen. Das Wichtigste dabei ist, dass der Browser die Seite schrittweise anzeigt - da er Fragmente der Antwort empfängt (dh fertige Skriptblöcke werden ausgeführt, sobald sie im Browser eintreffen). Dies bedeutet, dass es durchaus möglich ist, gleichzeitig große Datenmengen auf dem Server zu generieren und Skriptblöcke auf der Seite abzulegen, sobald die entsprechenden Daten bereit sind. Diese Skripte werden sofort auf dem Client ausgeführt. Dies ist die Grundlage des von Facebook verwendeten BigPipe-Systems. Dort werden viele unabhängige Pager parallel auf den Server geladen und an den Client übertragen, sobald sie verfügbar sind.

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

Wenn das Client-Skript bereit ist, die benötigten Daten anzufordern, überprüft es anstelle der XHR-Anforderung zunächst den JSON-Cache. Wenn der Cache bereits die Abfrageergebnisse enthält, erhält das Skript sofort das, was es benötigt. Wenn die Anforderung ausgeführt wird, wartet das Skript auf die Ergebnisse.

 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;    });  } } 

All dies führt dazu, dass der Vorgang des Ladens der Seite der gleiche ist wie in der folgenden Abbildung.


Der Vorgang des Ladens einer Seite in einer Situation, in der der Browser aktiv an der Vorbereitung der Daten für den Client beteiligt ist

Wenn Sie dies mit der einfachsten Methode zum Laden von Seiten vergleichen, stellt sich heraus, dass Server und Client jetzt mehr Aufgaben parallel ausführen können. Dies reduziert Ausfallzeiten, während der Server und Client aufeinander warten.

Diese Optimierung hat sich sehr positiv auf unser System ausgewirkt. In Desktop-Browsern begann das Laden der Seiten 14% schneller als zuvor. In mobilen Browsern (aufgrund längerer Verzögerungen in Mobilfunknetzen) wurde die Seite 23% schneller geladen.

Liebe Leser! Planen Sie, die hier in Ihren Projekten diskutierte Methode zur Optimierung der Bildung von Webseiten zu verwenden?


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


All Articles