Big Pipe: streaming e composição de páginas

Há muito tempo o time de engenharia do Facebook escreveu um post sobre uma técnica, conhecida como Big Pipe, que permitiu que eles melhorassem muito a performance das páginas da rede social. Esse ano, o Linkedin fez uma outra apresentação sobre a mesma técnica e como eles tiraram proveito para melhorar a performance de suas páginas.

Para quem chegou até aqui e não leu o post ou viu a apresentação, o problema basicamente é o seguinte: as páginas das duas empresas são tão complexas que se transformaram em gargalos de evolução dos projetos. No Linkedin, por exemplo, uma das páginas era tão grande que estourava a memória na hora de transformar uma JSP em um Servlet :). E esse é um problema realmente mais grave. Uma outra situação mais comum, é acabarmos com páginas usando vários includes ou várias tag files, sinal de um alto grau de complexidade. Caso use um framework MVC, estilo o VRaptor, pode dar uma olhada em métodos dos seus controllers cheios de result.include(…). Isso quer dizer que sua view está precisando de muitos objetos para ser montada, o que é um mal cheiro de código.

Existem algumas abordagens comuns para lidar com esse problema. Por exemplo, quebrar a página em várias e criar uma página principal com vários iframes. Em um exemplo hipotético, poderíamos quebrar a home da caelum da seguinte forma:

  <html>
      <head>
          <link ...>
          <script src=".."></script>
      </head>
      <body>
      <!-- Conteudo da pagina-->
      <iframe src="courses"/>
      <iframe src="calendar"/>
      <iframe src="blog"/>
      <!--resto do conteudo-->
      </body>
  </html>

Uma outra abordagem é ter a mesma página principal e fazer várias requisições ajax para ir buscando os conteúdos necessários para a montagem. Por mais que essas soluções resolvam o problema das páginas muito complicadas, um novo problema é gerado. O lado cliente fica responsável por fazer os requests de cada trecho de página, ou seja, o cliente é onerado por conta de um problema gerado no servidor. Cada trecho da página separado em um iframe ou carregado por ajax resulta em um novo request para o servidor:

iframes

É importante considerar que cada request feito do navegador ao servidor tem um custo razoável na performance de carregamento da página, pois cada requisição http requer que o navegador perca tempo abrindo um novo socket TCP com o servidor remoto. Sabemos que minimizar o número de requisições ao servidor é uma das práticas de otimização web mais importantes, como destacado neste post.

Com isso, as soluções de ajax e iframes se tornaram inviáveis para o Facebook. A equipe da rede social desenvolveu uma nova abordagem de quebrar uma página em várias: ao invés de deixar o lado cliente executar vários requests, o servidor fica responsável por fazer os vários requests e montar a página final. Assim, o navegador executa apenas uma requisição ao servidor que fica encarregado de construir a página com diversos requests locais:

pagelets

Pegamos carona nessa idéia e criamos um plugin para o VRaptor que permite fazer exatamente a mesma coisa. Confira o exemplo de página abaixo:

  <html>
      <head>
          <link ...>
          <script src=".."></script>
      </head>
      <body>
      <!-- Conteudo da pagina-->
      <div class="big-container home-secao" id="courses-pagelet">
      </div>
      <div class="big-container home-secao" id="blog-pagelet">
      </div>
      <div class="big-container home-calendario" id="calendar-pagelet">
      </div>
      <!--resto do conteudo-->
      </body>
      <vraptor:stream>
          <vraptor:page url="courses"/>
          <vraptor:page url="calendar"/>
          <vraptor:page url="blog"/>
      </vraptor:stream>
  </html>

Por debaixo do pano, tudo que não está dentro da tag stream será enviado imediatamente para o servidor. O plugin fica responsável por fazer um request para cada url passada e, na medida que os conteúdos forem sendo devolvidos, eles vão sendo enviados para o cliente. Para melhorar, todos os requests são feitos em paralelo e são enviados para o cliente na medida que vão chegando. O que leva a seguinte pergunta: caso o calendário dos cursos seja devolvido antes dos últimos posts no blog, como fazemos para posicioná-los corretamente?

Essa responsabilidade fica dentro do trecho de página devolvido, também chamado de Pagelet nos frameworks que implementam esta técnica. Perceba que na página principal deixamos algumas divs com com ids específicos.

    <div class="big-container home-secao" id="courses-pagelet">
    </div>
    <div class="big-container home-secao" id="blog-pagelet">
    </div>
    <div class="big-container home-calendario" id="calendar-pagelet">
    </div>

Agora o próprio pagelet é capaz de pegar o html gerado e posicionar no lugar correto.

    <noscript id="courses-content">
    <div class="home-empresas">
            <h2 class="home-subtitulo">Quem já treinou aqui</h2>
                <ul class="home-empresas-lista">
                    <li class="sp-home-petrobras home-empresa">Petrobras</li>
                    <li class="sp-home-samsung home-empresa">Samsung</li>
                </ul>
            </div>
            <div class="home-depoimentos"><h2 class="home-subtitulo">O que os alunos falam</h2>

                <div class="curso-depoimentos">
                    <div class="depoimento">
                        <blockquote>O treinamento dado pela Caelum foi fundamental e suficiente para viabilizar a implatação
                            do Scrum em minha equipe de Dev.
                        </blockquote>
                </div>
              </div>
    </div>
    </noscript>

    <script>
        var pagelet = document.getElementById("courses-content");
        document.getElementById("courses-pagelet").innerHTML = pagelet.textContent||pagelet.innerHTML;
    </script>

Existem 3 grandes vantagens nessa abordagem. A primeira é que o servidor consegue devolver alguma coisa para o cliente de uma maneira bem mais rápida, pois não precisa esperar processar todos os dados para montar a página. Ainda nessa linha, o servidor pode inclusive decidir em priorizar algum conteúdo, por exemplo no Facebook, geralmente a timeline é a primeira coisa a aparecer. A terceira vantagem é a manutenção dos códigos responsáveis por gerar os pagelets. Cada trecho de página ficará bem menor e muito mais fácil de testar.

Analisando mais a fundo, essa quebra já é bem praticada nos sistemas. Dividir o projeto em vários serviços web já é uma técnica muito utilizada no backend, agora estamos expandindo a mesma idéia para as suas páginas, afinal de contas elas são tão importantes quanto o resto :). Já começamos a utilizar técnica em um de nossos projetos, o GUJ. Atualmente existe a home padrão e a que utiliza o plugin. Com o firebug ou o chrome tools aberto, acesse as duas e veja as diferenças!

Guilherme Silveira vai fazer uma apresentação na QCon do Rio de Janeiro onde ele apresentará os detalhes de implementação dessa técnica! Caso esteja por lá, não perca!

15 Comentários

  1. Rafael Ponte 19/08/2014 at 09:14 #

    Muito interessante! Eu não havia lido sobre o assunto.

    Fiquei curioso com a implementação da técnica no plugin do VRaptor e vou dar uma olhada.

    Vocês tem utilizado isso em alguma aplicação ou site da Caelum? Os benefícios são aparentes para o usuário final? Existem corner-cases na qual deve-se tomar cuidado ao usar a técnica?

    Parabéns pelo post, Alberto e Sokol.

  2. Andre Fonseca 19/08/2014 at 09:40 #

    Existe a possibilidade de usar também a técnica de SSI. Onde o próprio servidor de front (apache ou nginx) faz a chamada para a view. Uso bastante essa técnica para coisas que tempo de cache diferentes, e com isso manter a entrega estática do todos com partes mais dinâmicas na página.

  3. Alberto Souza 19/08/2014 at 10:12 #

    Oi Rafael, utilizamos sim!. Dá uma olhada nas seguintes urls: http://www.webpagetest.org/result/140819_ZS_GS8/ e http://www.webpagetest.org/result/140819_S9_GT3/

    A segunda é a home do guj(guj.com.br/streamedHome) com o streamer. O first byte e o start render foram melhores :). Acho que o principal benefício para o usuário final é essa entrega de conteúdo de priorizado. Podíamos ter decidido que queríamos enviar a parte dos posts antes de tudo :).

    Uma parte que devemos tomar cuidado é o SEO. Como os pagelets vem fora de ordem, pode ser que afete um pouco na análise dos robôs dos motores de busca. Nas nossas pesquisas, os guidelines indicaram que apenas era necessário deixar o conteúdo numa tag noscript e, dessa forma, os robôs iam entender que aquele conteúdo ia ser posicionado via javascript.

  4. Daniel 21/08/2014 at 11:46 #

    Matéria sobre a caelum… Obrigado e parabéns ao grupo Caelum

    http://acuriosati.blogspot.com.br/2014/08/sem-sair-de-casa.html

  5. Frederico Maia Arantes 23/08/2014 at 14:37 #

    Shooow de bola! Já tinha usado bigpipe em uma app com Groovy e Grails, em uma rede social de música, o Soongz (https://soongz.com). Fantástico o VRaptor ter um plugin pra implementarmos facilmente essa técnica. Vou testar logo logo!

  6. Raphael Lacerda 02/09/2014 at 12:20 #

    niiiiceeeee!

    Cara, deixa eu ver se eu entendi bem. Isso está mais para um ajax reverso ou mais para o que o SPDY faz?
    Ou os dois? Ou nenhum nem outro?

    Já viram essa técnica ser aplicada para algum MVC ClienteSide estilo Angular?

  7. Raphael Lacerda 11/09/2014 at 10:45 #

    Uma dúvida

    “É importante considerar que cada request feito do navegador ao servidor tem um custo razoável na performance de carregamento da página, pois cada requisição http requer que o navegador perca tempo abrindo um novo socket TCP com o servidor remoto”

    Com o SPDY, ele mantém a conexão aberta… qual é o impacto do BigPipe em uma infra que tem SPDY?

  8. Alberto Souza 14/09/2014 at 20:12 #

    Oi Rapha, desculpa pela demora. Por mais que o recurso de multiplexing ajude muito, ele é utilizado apenas do lado do cliente. Uma vez que o html inteiro da página foi entregue, o cliente vai começar a baixar os recursos necessários com: imagens, css, js. Essas coisas vão ser feitas de forma paralela. O html, enviado pelo servidor, ainda é entregue de uma vez só.

    A sacada do big pipe é justamente trazer a multiplexação para o lado do servidor. Você pode enviar vários trechos do seu html para o navegador, de forma paralela… Claro que se isso fosse uma especificação, poderia ter um jeito de montar sua página sem o jeitinho do javascript. A junção dessa técnica, com o multiplexing do lado do cliente, deve deixar o carregamento da página ainda mais rápido. Além disso ainda temos a priorização de requests e o server push, oferecidos pelo spdy. Essa salada mista, pelo menos na minha opinião, leva o desempenho da sua página para outro nível. Porque dá para pensar em várias dessas coisas acontecendo do lado do servidor, durante o envio do seu html.

  9. Eliandro 19/12/2014 at 17:07 #

    Galera,
    Vocês sabem dizer se existe alguma implementação para Java?

  10. Chico Sokol 04/01/2015 at 17:26 #

    Oi Eliandro,

    Nós implementamos um plugin em Java para o VRaptor: https://github.com/asouza/vraptor-streamable-pages

    No final do post explicamos como se usa.

    abraço,

  11. Aline 25/02/2015 at 16:03 #

    Ola,

    O plugin é compatível com o wildfly?

  12. Alberto Souza 25/02/2015 at 16:05 #

    Oi Aline,

    Deveria ser sim!

  13. André 26/03/2015 at 11:35 #

    Muito bom o artigo e legal conhecer a técnica!

    Só fiquei na dúvida de uma coisa… acho que sei a resposta, mas melhor tirar a dúvida. 🙂

    Todo o html é gerado no servidor, é isso mesmo? Como é criado esse html no servidor? A montagem mesmo, concatenação de strings? E, se esses pequenos trechos de código tiver alguma interatividade de javascript?

    Abs e mais uma vez, parabéns!

  14. Alberto Souza 26/03/2015 at 11:39 #

    Oi André,

    O html é todo gerado no servidor. Caso o html do pagelet precise ser alterado por algum javascript, você pode deixar os dois juntos. Só lembrando que não concatenamos, apenas vamos escrevendo os trechos de html disponíveis no navegador. A arrumação dos trechos, que podem ser entregues desordenadamente, vai ficar a cargo do front.

  15. André 26/03/2015 at 12:02 #

    Olá Alberto! Obrigado pela resposta! 🙂

    Porém, não entendi essa parte: “Só lembrando que não concatenamos, apenas vamos escrevendo os trechos de html disponíveis no navegador”. Esses trechos de html estão onde? Vc vai ao servidor gerar o html. E, chegando no servidor, o html está onde? Por isso que pensei que houvesse algo como stringbuilder.add(“…”);

    Abs!

Deixe uma resposta