Aceleração instagram.com. Parte 3

Hoje publicamos uma tradução da terceira parte de uma série de materiais sobre a aceleração instagram.com. Na primeira parte, falamos sobre o pré-carregamento de dados; na segunda , sobre o envio de dados para o cliente por iniciativa do servidor. Isso é sobre cache.



O trabalho comeƧa com um cache


JÔ estamos enviando dados para o aplicativo cliente, fazendo isso durante o carregamento da pÔgina o mais cedo possível. Isso significa que a única maneira mais rÔpida de fornecer dados seria aquela que não fornece etapas envolvidas na solicitação de informações de um cliente ou no envio a um cliente por iniciativa do servidor. Isso pode ser feito usando essa abordagem para a formação da pÔgina, na qual o cache vem à tona. Isso, no entanto, significa que teremos que, embora muito brevemente, mostrar ao usuÔrio informações desatualizadas. Usando essa abordagem, depois de carregar a pÔgina, mostramos imediatamente ao usuÔrio uma cópia em cache de seu feed e histórias e, depois que os dados mais recentes estiverem disponíveis, substituímos tudo isso por esses dados.

Usamos o Redux para gerenciar o status do instagram.com. Como resultado, o plano geral de implementação do esquema acima se parece com isso. Armazenamos um subconjunto do repositório Redux no cliente, na tabela indexedDB, preenchendo esse repositório na primeira vez que a pÔgina é carregada. No entanto, trabalhar com o indexedDB, fazer o download de dados do servidor e a interação do usuÔrio com a pÔgina são processos assíncronos. Como resultado, podemos ter problemas. Eles consistem no fato de o usuÔrio estar trabalhando com o antigo estado em cache e precisamos fazer com que as ações do usuÔrio se apliquem ao novo estado ao recebê-lo do servidor.

Por exemplo, se usarmos os mecanismos padrão para trabalhar com o cache, podemos encontrar o seguinte problema. Estamos iniciando o carregamento paralelo de dados do cache e da rede. Como os dados do cache estarão prontos mais rapidamente que os dados da rede, mostramos ao usuÔrio. O usuÔrio então, por exemplo, gosta da postagem, mas após a resposta do servidor, que traz as informações mais recentes, chegar ao cliente, essas informações substituem as informações sobre a postagem curtida. Nestes novos dados, não haverÔ informações sobre os gostos que o usuÔrio definiu na versão em cache da postagem. Aqui estÔ como fica.


Estado de corrida que ocorre quando um usuÔrio interage com dados em cache (as ações Redux são destacadas em verde, o status é cinza)

Para resolver esse problema, precisÔvamos alterar o estado em cache de acordo com as ações do usuÔrio e salvar informações sobre essas ações, o que nos permitiria reproduzi-las conforme aplicadas ao novo estado recebido do servidor. Se você jÔ usou o Git ou outro sistema de controle de versão, essa tarefa pode lhe parecer familiar. Suponha que o estado em cache da fita seja a ramificação do repositório local e a resposta do servidor com os dados mais recentes seja a ramificação principal. Nesse caso, podemos dizer que queremos executar a operação de realocação, ou seja, queremos fazer as alterações registradas em uma ramificação (por exemplo, curtidas, comentÔrios etc.) e aplicÔ-las a outra.

Essa ideia nos leva Ć  seguinte arquitetura do sistema:

  • Quando a pĆ”gina Ć© carregada, enviamos uma solicitação ao servidor para baixar novos dados (ou aguardamos que sejam enviados por iniciativa do servidor).
  • Crie um subconjunto intermediĆ”rio (intermediĆ”rio) do estado Redux.
  • No processo de aguardar dados do servidor, salvamos as aƧƵes enviadas.
  • Depois de receber dados do servidor, executamos aƧƵes com os novos dados e reproduzimos as aƧƵes armazenadas nos novos dados, aplicando-as ao estado intermediĆ”rio.
  • Depois disso, confirmamos as alteraƧƵes e substituĆ­mos o estado atual por um estado intermediĆ”rio.


Solucionando um problema causado por uma condição de corrida usando um estado intermediÔrio (as ações Redux são destacadas em verde, o status é cinza)

GraƧas ao estado intermediƔrio, podemos reutilizar todos os redutores existentes. AlƩm disso, isso permite armazenar um estado intermediƔrio (que contƩm os dados mais recentes) separadamente do estado atual. E, como o trabalho com o estado intermediƔrio Ʃ implementado usando o Redux, basta enviar aƧƵes para usar esse estado!

API


A API de estado intermediƔrio consiste em duas funƧƵes principais. Isso Ʃ stagingAction e stagingCommit :

 function stagingAction(    key: string,    promise: Promise<Action>, ): AsyncAction<State, Action> function stagingCommit(key: string): AsyncAction<State, Action> 

Existem vÔrias outras funções lÔ, por exemplo, para cancelar alterações e lidar com casos de fronteira, mas não as consideraremos aqui.

A função stagingAction aceita uma promessa resolvida para um evento que precisa ser enviado para um estado intermediÔrio. Essa função inicializa um estado intermediÔrio e monitora as ações que foram enviadas desde sua inicialização. Se compararmos isso com os sistemas de controle de versão, verificamos que estamos lidando com a criação de uma filial local. As ações em andamento serão enfileiradas e aplicadas ao estado provisório após a chegada de novos dados.

A função stagingCommit substitui o estado atual por um intermediÔrio. Além disso, se for esperado que algumas operações assíncronas sejam concluídas e executadas em um estado intermediÔrio, o sistema aguardarÔ a conclusão dessas operações antes de substituí-las. Isso é semelhante à operação de realocação quando alterações locais (da ramificação que armazena o cache) são aplicadas sobre a ramificação principal (além dos novos dados recebidos do servidor), o que leva ao fato de que a versão local do estado é relevante.

Para habilitar o sistema de trabalho com um estado intermediÔrio, envolvemos o redutor de raiz nos recursos de extensão do redutor. Ele processa a ação stagingCommit e aplica ações salvas anteriormente ao novo estado. Para tirar proveito de tudo isso, precisamos apenas enviar ações e tudo o mais serÔ feito automaticamente. Por exemplo, se queremos carregar uma nova fita e colocar seus dados em um estado intermediÔrio, podemos fazer algo assim:

 function fetchAndStageFeed() {    return stagingAction(        'feed',        (async () => {            const {data} = await fetchFeedTimeline();            return {                type: FEED_LOADED,                ...data,            };        })(),    ); } //          store.dispatch(fetchAndStageFeed()); //   ,    stagingCommit, //      'feed' //      store.dispatch(stagingCommit('feed')); 

O uso de uma abordagem de renderização para o feed e as histórias, em que o cache vem à tona, acelerou a produção de materiais em 2,5% e 11%, respectivamente. Isso também contribuiu para o fato de que, na percepção dos usuÔrios, a versão web do sistema se aproximava dos clientes do Instagram para iOS e Android.

Caros leitores! VocĆŖ usa alguma abordagem para otimizar o armazenamento em cache ao trabalhar em seus projetos?


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


All Articles