A história do que foi lançado, como o impacto foi medido e as compensações que foram feitas.
Publicado em: 20 de junho de 2019
Pesquise quase qualquer assunto no Google e você vai encontrar uma página instantaneamente reconhecível com resultados relevantes e significativos. O que você provavelmente não percebeu é que essa página de resultados da pesquisa é, em determinados cenários, veiculada por uma tecnologia da Web poderosa chamada service worker.
A implantação da compatibilidade com service worker na Pesquisa Google sem afetar negativamente o desempenho exigiu dezenas de engenheiros trabalhando em várias equipes. Esta é a história do que foi lançado, como a performance foi medida e quais compensações foram feitas.
Principais motivos para conhecer os service workers
Adicionar um service worker a um web app, assim como fazer qualquer mudança arquitetônica no seu site, deve ser feito com um conjunto claro de metas em mente. Para a equipe da Pesquisa Google, havia alguns motivos principais para explorar a adição de um service worker.
Armazenamento em cache limitado dos resultados da pesquisa
A equipe da Pesquisa Google descobriu que é comum os usuários pesquisarem os mesmos termos mais de uma vez em um curto período. Em vez de acionar uma nova solicitação de back-end apenas para receber o que provavelmente seriam os mesmos resultados, a equipe de pesquisa queria aproveitar o armazenamento em cache e atender a essas solicitações repetidas localmente.
A importância da atualização não pode ser ignorada. Às vezes, os usuários pesquisam os mesmos termos repetidamente porque é um assunto em constante evolução e esperam ver resultados atualizados. Usar um service worker permite que a equipe de pesquisa implemente uma lógica refinada para controlar o tempo de vida dos resultados da pesquisa armazenados em cache localmente e alcançar o equilíbrio exato de velocidade e atualização que ela acredita ser o melhor para os usuários.
Experiência off-line significativa
Além disso, a equipe da Pesquisa Google queria oferecer uma experiência off-line significativa. Quando um usuário quer saber mais sobre um assunto, ele quer acessar direto a página da Pesquisa Google e começar a pesquisar, sem se preocupar com uma conexão de Internet ativa.
Sem um service worker, acessar a página de pesquisa do Google off-line só levaria à página de erro de rede padrão do navegador, e os usuários precisariam se lembrar de voltar e tentar de novo quando a conexão fosse restabelecida. Com um service worker, é possível veicular uma resposta HTML off-line personalizada e permitir que os usuários insiram a consulta de pesquisa imediatamente.

Os resultados não vão estar disponíveis até que haja uma conexão de Internet, mas o service worker permite que a pesquisa seja adiada e enviada aos servidores do Google assim que o dispositivo voltar a ficar on-line usando a API de sincronização em segundo plano.
Armazenamento em cache e disponibilização de JavaScript mais inteligentes
Outro motivo foi otimizar o armazenamento em cache e o carregamento do código JavaScript modularizado que alimenta os vários tipos de recursos na página de resultados da pesquisa. Há vários benefícios oferecidos pelo agrupamento do JavaScript que fazem sentido quando não há envolvimento do service worker. Por isso, a equipe de pesquisa não queria simplesmente parar de agrupar completamente.
Ao usar a capacidade de um service worker de versionar e armazenar em cache partes refinadas de JavaScript durante a execução, a equipe de pesquisa suspeitou que poderia reduzir a quantidade de rotatividade de cache e garantir que o JavaScript reutilizado no futuro possa ser armazenado em cache de maneira eficiente. A lógica no service worker pode analisar uma solicitação HTTP de saída para um pacote que contém vários módulos JavaScript e atender a ela juntando vários módulos armazenados em cache localmente, "desagrupando" quando possível. Isso economiza largura de banda do usuário e melhora a capacidade de resposta geral.
Há também benefícios de performance ao usar JavaScript em cache fornecido por um service worker: no Chrome, uma representação analisada de bytecode desse JavaScript é armazenada e reutilizada, o que reduz o trabalho a ser feito durante a execução para executar o JavaScript na página.
Desafios e soluções
Confira alguns dos obstáculos que precisaram ser superados para alcançar as metas declaradas da equipe. Embora alguns desses desafios sejam específicos da Pesquisa Google, muitos deles se aplicam a uma ampla variedade de sites que podem estar considerando uma implantação de service worker.
Problema: sobrecarga do service worker
O maior desafio, e o único obstáculo real para lançar um service worker na Pesquisa Google, foi garantir que ele não fizesse nada que pudesse aumentar a latência percebida pelo usuário. A Pesquisa Google leva a performance muito a sério e, no passado, bloqueou lançamentos de novas funcionalidades se elas contribuíssem com até dezenas de milissegundos de latência adicional para uma determinada população de usuários.
Quando a equipe começou a coletar dados de performance durante os primeiros experimentos, ficou óbvio que haveria um problema. O HTML retornado em resposta a solicitações de navegação para a página de resultados da pesquisa é dinâmico e varia muito dependendo da lógica que precisa ser executada nos servidores da Web da Pesquisa. No momento, não há como o service worker replicar essa lógica e retornar o HTML em cache imediatamente. O melhor que ele pode fazer é transmitir solicitações de navegação para os servidores da Web de back-end, o que exige uma solicitação de rede.
Sem um service worker, essa solicitação de rede acontece imediatamente após a navegação do usuário. Quando um service worker é registrado, ele sempre precisa ser iniciado
e ter a chance de executar os
processadores de eventos fetch,
mesmo quando não há chance de esses processadores de busca fazerem algo além de ir
para a rede. O tempo necessário para iniciar e executar o código do service
worker é uma sobrecarga pura adicionada a cada navegação:

Isso coloca a implementação do service worker em uma desvantagem de latência muito grande para justificar outros benefícios. Além disso, a equipe descobriu que, com base na medição dos tempos de inicialização do service worker em dispositivos reais, havia uma ampla distribuição de tempos de inicialização. Alguns dispositivos móveis de baixo custo levavam quase o mesmo tempo para iniciar o service worker do que para fazer a solicitação de rede do HTML da página de resultados.
Solução: usar a pré-carga de navegação
O recurso único e mais importante que permitiu à equipe da Pesquisa Google avançar com o lançamento do service worker é a pré-carga de navegação. Usar o pré-carregamento de navegação é uma vitória importante de performance para qualquer service worker que precise usar uma resposta da rede para atender a solicitações de navegação. Ele fornece uma dica ao navegador para iniciar a solicitação de navegação imediatamente, ao mesmo tempo em que o service worker é iniciado:

Desde que o tempo necessário para a inicialização do service worker seja menor do que o tempo necessário para receber uma resposta da rede, não haverá sobrecarga de latência introduzida pelo service worker.
A equipe de pesquisa também precisava evitar o uso de um service worker em dispositivos móveis de baixo custo, em que o tempo de inicialização do service worker poderia exceder a solicitação de navegação. Como não há uma regra rígida para o que constitui um dispositivo "de baixo custo", eles criaram a heurística de verificar a RAM total instalada no dispositivo. Qualquer coisa com menos de 2 gigabytes de memória se enquadrava na categoria de dispositivos de baixo custo, em que o tempo de inicialização do service worker seria inaceitável.
O espaço de armazenamento disponível é outra consideração, já que o conjunto completo de
recursos a serem armazenados em cache para uso futuro pode chegar a vários megabytes. A
interface navigator.storage
permite que a página da Pesquisa Google descubra com antecedência se as tentativas de
armazenar dados em cache correm o risco de falhar devido a problemas na cota de armazenamento.
Isso deixou a equipe de pesquisa com vários critérios que ela poderia usar para determinar se um service worker seria usado ou não: se um usuário acessar a página da Pesquisa Google usando um navegador que oferece suporte à pré-carga de navegação e tiver pelo menos 2 gigabytes de RAM e espaço de armazenamento livre suficiente, um service worker será registrado. Navegadores ou dispositivos que não atendem a esse critério não terão um service worker, mas ainda vão aproveitar a mesma experiência da Pesquisa Google de sempre.
Um benefício secundário desse registro seletivo é a capacidade de enviar um service worker menor e mais eficiente. O direcionamento a navegadores razoavelmente modernos para executar o código do service worker elimina a sobrecarga de transpilagem e polyfills para navegadores mais antigos. Isso acabou cortando cerca de 8 kilobytes de código JavaScript não compactado do tamanho total da implementação do service worker.
Problema: escopos do service worker
Depois que a equipe de pesquisa fez experimentos suficientes de latência e teve certeza de que o uso do pré-carregamento de navegação oferecia um caminho viável e neutro em relação à latência para usar um service worker, alguns problemas práticos começaram a surgir. Um desses problemas tem a ver com as regras de escopo do service worker. O escopo de um service worker determina quais páginas ele pode controlar.
O escopo funciona com base no prefixo do caminho do URL. Para domínios que hospedam um único
app da Web, isso não é um problema, já que normalmente você usaria um service worker com
o escopo máximo de /, que poderia assumir o controle de qualquer página no domínio.
Mas a estrutura de URL da Pesquisa Google é um pouco mais complicada.
Se o service worker recebesse o escopo máximo de /, ele acabaria
controlando qualquer página hospedada em www.google.com (ou no equivalente
regional), e há URLs nesse domínio que não têm nada a ver com a
Pesquisa Google. Um escopo mais razoável e restritivo seria /search, que pelo menos eliminaria URLs completamente não relacionados aos resultados da pesquisa.
Infelizmente, até mesmo esse caminho de URL /search é compartilhado entre diferentes tipos de resultados da Pesquisa Google, com parâmetros de consulta de URL determinando qual tipo específico de resultado da pesquisa é mostrado. Algumas dessas versões usam bases de código completamente diferentes da página de resultados da pesquisa na Web tradicional. Por exemplo, a Pesquisa de imagens e a Pesquisa do Google Shopping são veiculadas no caminho do URL /search com parâmetros de consulta diferentes, mas nenhuma dessas interfaces estava pronta para lançar a própria experiência de service worker (ainda).
Solução: criar uma estrutura de expedição e roteamento
Embora existam algumas propostas que permitem algo mais eficiente do que prefixos de caminho de URL para determinar escopos de service worker, a equipe da Pesquisa Google não conseguia implantar um service worker que não fazia nada para um subconjunto de páginas que ele controlava.
Para contornar isso, a equipe da Pesquisa Google criou uma estrutura de envio e roteamento personalizada que podia ser configurada para verificar critérios como os parâmetros de consulta da página do cliente e usar esses dados para determinar qual caminho de código específico seguir. Em vez de codificar regras, o sistema foi criado para ser flexível e permitir que equipes que compartilham o espaço de URL, como a Pesquisa de imagens e a Pesquisa Google Shopping, incluam a própria lógica de service worker no futuro, se decidirem implementar.
Problema: resultados e métricas personalizados
Os usuários podem fazer login na Pesquisa Google usando as Contas do Google, e a experiência de resultados da pesquisa pode ser personalizada com base nos dados específicos da conta. Os usuários conectados são identificados por cookies específicos do navegador, um padrão consagrado e amplamente aceito.
No entanto, uma desvantagem de usar cookies do navegador é que eles não são expostos em um service worker, e não há como examinar automaticamente os valores e garantir que eles não mudaram devido a um usuário saindo ou trocando de conta. (Há um esforço em andamento para trazer acesso a cookies para service workers, mas, no momento da redação deste artigo, a abordagem é experimental e não é amplamente compatível.)
Uma incompatibilidade entre a visualização do usuário conectado atual do service worker e o usuário real conectado à interface da Web da Pesquisa Google pode levar a resultados de pesquisa personalizados incorretamente ou métricas e registros atribuídos de forma errada. Qualquer um desses cenários de falha seria um problema grave para a equipe da Pesquisa Google.
Solução: enviar cookies usando postMessage
Em vez de esperar o lançamento de APIs experimentais e fornecer acesso direto aos cookies do navegador em um service worker, a equipe da Pesquisa Google optou por uma solução provisória: sempre que uma página controlada pelo service worker é carregada, ela lê os cookies relevantes e usa postMessage() para enviá-los ao service worker.
Em seguida, o service worker verifica o valor atual do cookie em relação ao valor esperado e, se houver uma incompatibilidade, toma medidas para limpar todos os dados específicos do usuário do armazenamento e recarrega a página de resultados da pesquisa sem personalização incorreta.
As etapas específicas que o service worker realiza para redefinir as coisas a um valor de referência são particulares aos requisitos da Pesquisa Google, mas a mesma abordagem geral pode ser útil para outros desenvolvedores que trabalham com dados personalizados com base em cookies do navegador.
Problema: experimentos e dinamismo
Como mencionado, a equipe da Pesquisa Google depende muito da execução de experimentos em produção e do teste dos efeitos de novos códigos e recursos no mundo real antes de ativá-los por padrão. Isso pode ser um desafio com um service worker estático que depende muito de dados armazenados em cache, já que a ativação e desativação de usuários em experimentos geralmente exigem comunicação com o servidor de back-end.
Solução: script de service worker gerado dinamicamente
A solução escolhida pela equipe foi usar um script de service worker gerado dinamicamente, personalizado pelo servidor da Web para cada usuário individual, em vez de um único script estático gerado com antecedência. As informações sobre experimentos que podem afetar o comportamento do service worker ou solicitações de rede em geral são incluídas diretamente nesses scripts personalizados. A mudança dos conjuntos de experiências ativas para um usuário é feita com uma combinação de técnicas tradicionais, como cookies do navegador, e veiculação de código atualizado no URL do service worker registrado.
Usar um script de service worker gerado dinamicamente também facilita fornecer uma saída de emergência no caso improvável de uma implementação ter um bug fatal que precisa ser evitado. A resposta dinâmica do service worker pode ser uma implementação no-op, desativando o service worker para alguns ou todos os usuários atuais.
Problema: coordenação de atualizações
Um dos desafios mais difíceis enfrentados por qualquer implantação de service worker no mundo real é criar uma troca razoável entre evitar a rede em favor do cache e, ao mesmo tempo, garantir que os usuários atuais recebam atualizações críticas e mudanças logo após a implantação na produção. O equilíbrio certo depende de muitos fatores:
- Se o web app for um aplicativo de página única de longa duração que um usuário mantém aberto indefinidamente, sem navegar para novas páginas.
- Qual é a cadência de implantação para atualizações no seu servidor da Web de back-end.
- Se o usuário médio toleraria usar uma versão um pouco desatualizada do seu web app ou se a atualização é a principal prioridade.
Ao fazer experimentos com service workers, a equipe da Pesquisa Google garantiu que eles fossem executados em várias atualizações programadas de back-end. Assim, as métricas e a experiência do usuário corresponderiam mais de perto ao que os usuários recorrentes veriam no mundo real.
Solução: equilibrar a atualização e a utilização do cache
Depois de testar várias opções de configuração diferentes, a equipe da Pesquisa Google descobriu que a configuração a seguir oferecia o equilíbrio certo entre atualização e utilização do cache.
O URL do script do service worker é veiculado com o cabeçalho de resposta
Cache-Control: private, max-age=1500 (1.500 segundos ou 25 minutos) e é
registrado com updateViaCache definido como "all"
para garantir que o cabeçalho seja respeitado. Como você pode imaginar, o back-end da Web da Pesquisa Google é um grande conjunto de servidores distribuídos globalmente que exige um tempo de atividade de quase 100%. A implantação de uma mudança que afetaria o conteúdo do script do service worker é feita de maneira gradual.
Se um usuário acessar um back-end atualizado e navegar rapidamente para outra página que acessa um back-end que ainda não recebeu o service worker atualizado, ele vai alternar entre as versões várias vezes. Portanto, dizer ao navegador para verificar um script atualizado apenas se 25 minutos tiverem passado desde a última verificação não tem uma desvantagem significativa. A vantagem de ativar esse comportamento é reduzir significativamente o tráfego recebido pelo endpoint que gera dinamicamente o script do service worker.
Além disso, um cabeçalho ETag é definido na resposta HTTP do script do service worker, garantindo que, quando uma verificação de atualização for feita após 25 minutos, o servidor possa responder de maneira eficiente com uma resposta HTTP 304 se não houver atualizações no service worker implantado nesse período.
Embora algumas interações no web app da Pesquisa Google usem navegações de estilo de app
de página única (ou seja, pela
API History),
na maior parte, a Pesquisa Google é um web app tradicional que usa navegações "reais". Isso entra em jogo quando a equipe decide que é eficaz usar duas opções que aceleram o ciclo de vida de atualização do service worker: clients.claim() e skipWaiting().
Ao clicar na interface da Pesquisa Google, você geralmente navega até novos documentos HTML. Chamar skipWaiting garante que um service worker atualizado
tenha a chance de processar essas novas solicitações de navegação imediatamente após
a instalação. Da mesma forma, chamar clients.claim() significa que o service worker atualizado
terá a chance de começar a controlar todas as páginas abertas da Pesquisa Google
que não estão controladas, seguindo a ativação do service worker.
A abordagem usada pela Pesquisa Google não é necessariamente uma solução que funciona para todos. Ela foi o resultado de testes A/B cuidadosos com várias combinações de opções de veiculação até encontrar o que funcionava melhor para a empresa.
Os desenvolvedores cuja infraestrutura de back-end permite implantar atualizações mais
rapidamente podem preferir que o navegador verifique um script de service worker atualizado
com a maior frequência possível, sempre ignorando o cache HTTP.
Se você estiver criando um app de página única que os usuários podem manter aberto por muito tempo, usar skipWaiting() provavelmente não é a escolha certa. Você corre o risco de ter inconsistências de cache se permitir que o novo service worker seja ativado enquanto houver clientes de longa duração.
Pontos-chave
Por padrão, os service workers não são neutros em termos de desempenho
Adicionar um service worker ao seu web app significa inserir um trecho adicional de JavaScript que precisa ser carregado e executado antes que o web app receba respostas às solicitações. Se essas respostas vierem de um cache local em vez da rede, a sobrecarga de execução do service worker geralmente será insignificante em comparação com o ganho de desempenho da estratégia cache-first. Mas se você sabe que o service worker sempre precisa consultar a rede ao processar solicitações de navegação, usar o pré-carregamento de navegação é uma melhoria de desempenho crucial.
Os service workers ainda são um aprimoramento progressivo
O suporte a service workers está muito melhor hoje do que há um ano. Todos os navegadores modernos agora oferecem pelo menos algum suporte a service workers, mas, infelizmente, alguns recursos avançados, como sincronização em segundo plano e pré-carregamento de navegação, não são disponibilizados universalmente. A verificação de recursos para o subconjunto específico de recursos que você sabe que precisa e o registro de um service worker somente quando eles estão presentes ainda é uma abordagem razoável.
Da mesma forma, se você fez experimentos em situações reais e sabe que dispositivos de baixo custo têm um desempenho ruim com a sobrecarga adicional de um service worker, evite registrar um service worker nesses cenários também.
Continue tratando os service workers como um aprimoramento progressivo que é adicionado a um web app quando todos os pré-requisitos são atendidos e o service worker adiciona algo positivo à experiência do usuário e ao desempenho geral de carregamento.
Medir tudo
A única maneira de descobrir se o envio de um service worker teve um impacto positivo ou negativo nas experiências dos usuários é testar e medir os resultados.
As especificidades da configuração de medições significativas dependem do provedor de análise que você está usando e de como normalmente realiza experimentos na configuração de implantação. Uma abordagem, que usa o Google Analytics para coletar métricas, é detalhada neste estudo de caso (em inglês) com base na experiência de uso de service workers no web app do Google I/O.
Não metas
Embora muitos na comunidade de desenvolvimento da Web associem service workers a Progressive Web Apps, criar um "PWA da Pesquisa Google" não era uma meta inicial da equipe. O app Google Pesquisa não fornece metadados em um manifesto de app da Web, nem incentiva os usuários a passar pelo fluxo "Adicionar à tela inicial". A equipe de pesquisa está satisfeita com os usuários que acessam o web app com os pontos de entrada clássicos da Pesquisa Google.
Em vez de tentar transformar a experiência da Web da Pesquisa Google no equivalente ao que você esperaria de um aplicativo instalado, o foco no lançamento inicial foi melhorar progressivamente o site atual.
Agradecimentos
Agradecemos a toda a equipe de desenvolvimento da Web da Pesquisa Google pelo trabalho na implementação do service worker e por compartilhar o material de apoio que foi usado para escrever este artigo. Um agradecimento especial a Philippe Golle, Rajesh Jagannathan, R. Samuel Klatchko, Andy Martone, Leonardo Peña, Rachel Shearer, Greg Terrono e Clay Woolam.
Atualização (outubro de 2021): desde a publicação original deste artigo, a equipe da Pesquisa Google reavaliou os benefícios e as compensações da arquitetura atual do service worker. O service worker descrito acima está sendo desativado. À medida que a infraestrutura da Web da Pesquisa Google evolui, a equipe pode revisar o design do service worker.