Heute veröffentlichen wir eine Übersetzung des dritten Teils einer Reihe von Materialien über Acceleration instagram.com. Im
ersten Teil haben wir über das Vorladen von Daten gesprochen, im
zweiten über das Senden von Daten an den Client auf Initiative des Servers. Hier geht es um Caching.

Die Arbeit beginnt mit einem Cache
Wir senden bereits Daten an die Client-Anwendung und tun dies so früh wie möglich beim Laden der Seite. Dies bedeutet, dass der einzige schnellere Weg zur Bereitstellung von Daten ein Schritt ist, bei dem überhaupt keine Schritte erforderlich sind, um Informationen vom Client anzufordern oder auf Initiative des Servers an den Client zu senden. Dies kann mit diesem Ansatz zur Seitenbildung erfolgen, bei dem der Cache in den Vordergrund tritt. Dies bedeutet jedoch, dass wir dem Benutzer, wenn auch nur sehr kurz, veraltete Informationen anzeigen müssen. Bei diesem Ansatz zeigen wir dem Benutzer nach dem Laden der Seite sofort eine zwischengespeicherte Kopie seines Feeds und seiner Storys. Sobald die neuesten Daten verfügbar sind, ersetzen wir diese durch solche Daten.
Wir verwenden Redux, um den Status von instagram.com zu verwalten. Infolgedessen sieht der Gesamtimplementierungsplan des obigen Schemas so aus. Wir speichern eine Teilmenge des Redux-Repositorys auf dem Client in der indexedDB-Tabelle und füllen dieses Repository beim ersten Laden der Seite. Das Arbeiten mit indexedDB, das Herunterladen von Daten vom Server und die Benutzerinteraktion mit der Seite sind jedoch asynchrone Prozesse. Infolgedessen können Probleme auftreten. Sie bestehen in der Tatsache, dass der Benutzer mit dem alten zwischengespeicherten Status arbeitet und wir die Aktionen des Benutzers auf den neuen Status anwenden müssen, wenn er vom Server empfangen wird.
Wenn wir beispielsweise die Standardmechanismen für die Arbeit mit dem Cache verwenden, kann das folgende Problem auftreten. Wir beginnen mit dem parallelen Laden von Daten aus dem Cache und aus dem Netzwerk. Da die Daten aus dem Cache schneller bereit sind als die Netzwerkdaten, zeigen wir sie dem Benutzer. Der Benutzer mag dann beispielsweise den Beitrag, aber nachdem die Antwort des Servers, die die neuesten Informationen enthält, beim Client eingeht, überschreiben diese Informationen die Informationen über den gewünschten Beitrag. In diesen neuen Daten gibt es keine Informationen darüber, wie der Benutzer die zwischengespeicherte Version des Beitrags festgelegt hat. So sieht es aus.
Race-Status, der auftritt, wenn ein Benutzer mit zwischengespeicherten Daten interagiert (Redux-Aktionen werden grün hervorgehoben, Status ist grau)Um dieses Problem zu lösen, mussten wir den zwischengespeicherten Status entsprechend den Aktionen des Benutzers ändern und Informationen zu diesen Aktionen speichern, damit wir sie vom Server auf den neuen Status anwenden können. Wenn Sie jemals Git oder ein anderes Versionskontrollsystem verwendet haben, kommt Ihnen diese Aufgabe möglicherweise bekannt vor. Angenommen, der zwischengespeicherte Status des Bandes ist der lokale Repository-Zweig, und die Serverantwort mit den neuesten Daten ist der Hauptzweig. Wenn ja, können wir sagen, dass wir die Verschiebungsoperation ausführen möchten, dh wir möchten die in einem Zweig aufgezeichneten Änderungen (z. B. Likes, Kommentare usw.) übernehmen und auf einen anderen anwenden.
Diese Idee führt uns zu folgender Systemarchitektur:
- Wenn die Seite geladen wird, senden wir eine Anfrage an den Server, um neue Daten herunterzuladen (oder warten, bis sie auf Initiative des Servers gesendet werden).
- Erstellen Sie eine Zwischenstufe (inszeniert) des Redux-Status.
- Während des Wartens auf Daten vom Server speichern wir die übermittelten Aktionen.
- Nachdem wir Daten vom Server empfangen haben, führen wir Aktionen mit den neuen Daten aus und spielen die gespeicherten Aktionen für die neuen Daten ab und wenden sie auf den Zwischenzustand an.
- Danach übernehmen wir die Änderungen und ersetzen den aktuellen Status durch einen Zwischenstatus.
Lösen eines Problems, das durch eine Rennbedingung verursacht wurde, unter Verwendung eines Zwischenzustands (Redux-Aktionen werden grün hervorgehoben, Status ist grau)Dank des Zwischenzustands können wir alle vorhandenen Reduzierstücke wiederverwenden. Auf diese Weise können Sie außerdem einen Zwischenstatus (der die neuesten Daten enthält) getrennt vom aktuellen Status speichern. Und da die Arbeit mit dem Zwischenzustand mit Redux implementiert wird, reicht es aus, Aktionen zu senden, um diesen Zustand zu verwenden!
API
Die Zwischenzustands-API besteht aus zwei Hauptfunktionen. Dies ist
stagingAction
und
stagingCommit
:
function stagingAction( key: string, promise: Promise<Action>, ): AsyncAction<State, Action> function stagingCommit(key: string): AsyncAction<State, Action>
Es gibt dort mehrere andere Funktionen, zum Beispiel zum Abbrechen von Änderungen und zum Behandeln von Grenzfällen, aber wir werden sie hier nicht berücksichtigen.
Die Funktion
stagingAction
akzeptiert ein Versprechen, das in ein Ereignis aufgelöst wird, das an einen Zwischenzustand gesendet werden muss. Diese Funktion initialisiert einen Zwischenzustand und überwacht die Aktionen, die seit ihrer Initialisierung gesendet wurden. Wenn wir dies mit Versionskontrollsystemen vergleichen, stellt sich heraus, dass es sich um die Erstellung einer lokalen Niederlassung handelt. Die laufenden Aktionen werden in die Warteschlange gestellt und auf den Zwischenstatus angewendet, nachdem neue Daten eingegangen sind.
Die Funktion
stagingCommit
ersetzt den aktuellen Status durch einen Zwischenstatus. Wenn erwartet wird, dass einige asynchrone Vorgänge abgeschlossen sind, die in einem Zwischenzustand ausgeführt werden, wartet das System außerdem, bis diese Vorgänge abgeschlossen sind, bevor sie ersetzt werden. Dies ähnelt dem Verschiebungsvorgang, wenn lokale Änderungen (von dem Zweig, in dem der Cache gespeichert ist) auf den Hauptzweig angewendet werden (zusätzlich zu den vom Server empfangenen neuen Daten), was dazu führt, dass die lokale Version des Status aktuell ist.
Um das Arbeitssystem mit einem Zwischenzustand zu ermöglichen, haben wir den Root-Reduzierer in die Extender-Funktionen des Reduzierers eingewickelt. Es verarbeitet die Aktion
stagingCommit
und wendet zuvor gespeicherte Aktionen auf den neuen Status an. Um all dies zu nutzen, müssen wir nur Aktionen senden, und alles andere wird automatisch erledigt. Wenn wir beispielsweise ein neues Band laden und seine Daten in einen Zwischenzustand versetzen möchten, können wir Folgendes tun:
function fetchAndStageFeed() { return stagingAction( 'feed', (async () => { const {data} = await fetchFeedTimeline(); return { type: FEED_LOADED, ...data, }; })(), ); } // store.dispatch(fetchAndStageFeed()); // , stagingCommit, // 'feed' // store.dispatch(stagingCommit('feed'));
Die Verwendung eines Rendering-Ansatzes für den Feed und die Storys, bei denen der Cache im Vordergrund steht, hat die Materialausgabe um 2,5% bzw. 11% beschleunigt. Dies trug außerdem dazu bei, dass die Webversion des Systems in der Wahrnehmung der Benutzer den Instagram-Clients für iOS und Android näher kam.
Liebe Leser! Verwenden Sie Ansätze, um das Caching bei der Arbeit an Ihren Projekten zu optimieren?
