Aceleração instagram.com. Parte 2

Hoje, chamamos a atenção para a tradução do segundo material de uma série dedicada à otimização do instagram.com. Aqui vamos nos concentrar em melhorar o mecanismo para a execução antecipada de consultas GraphQL e em aumentar a eficiência da transmissão de dados HTML para o cliente.



→ Leia com a respiração suspensa, a primeira parte

Envio de dados iniciado pelo servidor ao cliente usando a tecnologia progressiva de download de HTML


Na primeira parte, falamos sobre como, usando mecanismos de pré-carregamento, iniciar a execução de consultas nos estágios iniciais do processamento da página. Ou seja, mesmo antes do script que inicia essas solicitações ser carregado. Diante disso, pode-se notar que a execução dessas solicitações no estágio de pré-carregamento de materiais ainda significava que sua execução não foi iniciada antes da renderização da página HTML no cliente. E isso, por sua vez, significava que a solicitação não podia ser iniciada antes que o cliente enviasse uma solicitação ao servidor e o servidor respondesse a essa solicitação (aqui você também precisa adicionar o tempo que o servidor leva para gerar uma resposta HTML ao cliente). Na figura a seguir, você pode ver que o início de uma consulta GraphQL pode demorar bastante. E isso é - dado que começamos a executar essas solicitações usando o código localizado na tag HTML <head> e que essa é uma das primeiras tarefas que resolvemos com a ajuda das ferramentas de pré-carregamento de dados.


A execução preliminar da solicitação começa com um atraso perceptível

Em teoria, o início de uma consulta do GraphQL seria ideal para o momento em que uma solicitação para carregar a página correspondente fosse enviada ao servidor. Mas como fazer o navegador começar a baixar algo antes mesmo de receber pelo menos algum código HTML do servidor? A resposta é enviar o recurso para o navegador por iniciativa do servidor. Pode parecer que, para implementar esse mecanismo, você precisará de algo como HTTP / 2 Server Push. Mas, de fato, existe uma tecnologia muito antiga (que geralmente é esquecida) que permite implementar um esquema semelhante de interação entre o cliente e o servidor. Essa tecnologia é caracterizada pelo suporte universal ao navegador; para sua implementação, não há necessidade de aprofundar as complexidades de infraestrutura típicas da implementação do HTTP / 2 Server Push. O Facebook usa essa tecnologia desde 2010 (leia sobre o BigPipe ) e, em outros sites como o Ebay, também encontra aplicativos de várias formas. Mas parece que os desenvolvedores JavaScript de aplicativos de página única basicamente ignoram essa tecnologia ou simplesmente não a usam. Trata-se de carregar progressivamente HTML. Essa tecnologia é conhecida sob vários nomes: "descarga antecipada", "descarga da cabeça", "HTML progressivo". Funciona graças a uma combinação de dois mecanismos:

  • A primeira é a codificação de transferência em pedaços HTTP.
  • O segundo é a renderização progressiva do HTML no navegador.

O mecanismo de codificação de transferência em pedaços apareceu no HTTP / 1.1. Ele permite que você divida as respostas HTTP em várias partes pequenas que são transmitidas ao navegador no modo de streaming. O navegador "prende" essas partes quando elas chegam, formando o código de resposta completo delas. Embora essa abordagem preveja alterações significativas na forma como as páginas são formadas no servidor, a maioria dos idiomas e estruturas tem a capacidade de fornecer respostas semelhantes, divididas em partes. As interfaces da Web do Instagram usam o Django, então usamos o objeto StreamingHttpResponse . A razão pela qual o uso desse mecanismo pode ser benéfico é que ele permite enviar o conteúdo HTML da página para o navegador no modo de streaming, pois partes individuais da página estão prontas, em vez de esperar que o código da página inteira esteja pronto. Isso significa que podemos liberar o título da página do navegador quase instantaneamente após o recebimento da solicitação (daí o termo "liberação antecipada"). A preparação do cabeçalho não requer recursos de servidor particularmente grandes. Isso permite que o navegador comece a carregar scripts e estilos, mesmo quando o servidor estiver ocupado gerando dados dinâmicos para o restante da página. Vamos dar uma olhada em que efeito essa técnica tem. É assim que um carregamento de página normal se parece.


A tecnologia de liberação antecipada não é usada: o carregamento de recursos não inicia até que o HTML da página esteja totalmente carregado

Mas o que acontece se o servidor, após o recebimento da solicitação, passa imediatamente o título da página para o navegador.


A tecnologia de liberação antecipada é usada: os recursos começam a carregar imediatamente após as tags HTML serem despejadas no navegador

Além disso, podemos usar o mecanismo de envio de mensagens HTTP em partes para enviar dados ao cliente quando eles estiverem prontos. No caso de aplicativos que são renderizados no servidor, esses dados podem ser apresentados na forma de código HTML. Mas se estamos falando de aplicativos de página única como instagram.com, o servidor também pode transmitir algo como dados JSON para o cliente. Para dar uma olhada em como isso funciona, vamos dar uma olhada no exemplo mais simples de como iniciar um aplicativo de página única.

Primeiro, a marcação HTML original é enviada ao navegador que contém o código JavaScript necessário para renderizar a página. Após analisar e executar esse script, uma solicitação XHR será executada, carregando os dados de origem necessários para renderizar a página.


O processo de carregar uma página em uma situação em que o navegador solicita independentemente do servidor tudo o que ele precisa

Esse processo envolve várias situações nas quais o cliente envia uma solicitação ao servidor e aguarda uma resposta dele. Como resultado, há períodos em que o servidor e o cliente estão inativos. Em vez de esperar o servidor aguardar a solicitação de API do cliente, seria mais eficaz se o servidor começasse a trabalhar na preparação de uma resposta de API imediatamente após a geração do código HTML. Depois que a resposta estivesse pronta, o servidor poderia, por sua própria iniciativa, envenená-la para o cliente. Isso significa que, quando o cliente tiver preparado tudo o que é necessário para visualizar os dados que foram carregados anteriormente após a conclusão da solicitação da API, é provável que esses dados estejam prontos. O cliente não precisaria atender uma solicitação separada ao servidor e aguardar uma resposta dele.

A primeira etapa na implementação de um esquema de interação cliente-servidor é criar um cache JSON projetado para armazenar respostas do servidor. Desenvolvemos essa parte do sistema usando um pequeno bloco de scripts incorporado no código HTML da página. Ele desempenha o papel de cache e contém informações sobre solicitações que serão adicionadas ao cache pelo servidor (isso, de forma simplificada, é mostrado abaixo).

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

Após redefinir o código HTML para o navegador, o servidor pode executar solicitações de API independentemente. Depois de receber respostas para essas solicitações, o servidor despejará dados JSON na página na forma de uma tag de script que contém esses dados. Quando o navegador recebe e analisa um fragmento semelhante do código HTML da página, isso leva ao fato de que os dados caem no cache JSON. O mais importante aqui é que o navegador exibirá a página progressivamente - à medida que recebe fragmentos da resposta (ou seja, os blocos finais de scripts serão executados assim que chegarem no navegador). Isso significa que é possível gerar simultaneamente grandes quantidades de dados no servidor e soltar blocos de script na página assim que os dados correspondentes estiverem prontos. Esses scripts serão executados imediatamente no cliente. Essa é a base do sistema BigPipe usado pelo Facebook. Lá, muitos pagers independentes são carregados em paralelo no servidor e transmitidos ao cliente assim que ficam disponíveis.

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

Quando o script do cliente está pronto para solicitar os dados necessários, ele, em vez de executar a solicitação XHR, primeiro verifica o cache JSON. Se o cache já tiver os resultados da consulta, o script receberá imediatamente o que precisa. Se a solicitação estiver em andamento, o script estará aguardando os resultados.

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

Tudo isso leva ao fato de que o processo de carregamento da página se torna o mesmo que no diagrama a seguir.


O processo de carregamento de uma página em uma situação em que o navegador está envolvido ativamente na preparação de dados para o cliente

Se você comparar isso com a maneira mais fácil de carregar páginas, o servidor e o cliente agora poderão executar mais tarefas em paralelo. Isso reduz o tempo de inatividade durante o qual o servidor e o cliente esperam um pelo outro.

Essa otimização teve um efeito muito positivo em nosso sistema. Portanto, nos navegadores de desktop, o carregamento da página começou a ser concluído 14% mais rápido do que antes. E nos navegadores móveis (devido a atrasos maiores nas redes móveis), a página começou a carregar 23% mais rapidamente.

Caros leitores! Você planeja usar a metodologia para otimizar a formação de páginas da web discutidas aqui em seus projetos?


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


All Articles