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?
