A história do que foi enviado, como o impacto foi medido e as compensações feitas.
Contexto
Pesquise qualquer tópico no Google e você vai encontrar uma página imediatamente reconhecível de resultados significativos e relevantes. O que você provavelmente não percebeu é que essa página de resultados da pesquisa é, em determinados cenários, servida por uma tecnologia da Web poderosa chamada de service worker.
O lançamento do suporte de worker de serviço para a Pesquisa Google sem afetar negativamente a performance exigiu dezenas de engenheiros trabalhando em várias equipes. Esta é a história do que foi enviado, como a performance foi medida e quais compromissos foram feitos.
Principais motivos para conhecer os service workers
Adicionar um worker de serviço a um app da Web, assim como fazer qualquer alteração de arquitetura no seu site, precisa ser feito com um conjunto claro de metas em mente. Para a equipe de Pesquisa do Google, havia alguns motivos importantes para adicionar um worker de serviço.
Armazenamento em cache de resultados de pesquisa limitado
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 os resultados mais prováveis, a equipe de pesquisa quis aproveitar o armazenamento em cache e atender a essas solicitações repetidas localmente.
A importância da atualidade não pode ser descartada. Às vezes, os usuários pesquisam os mesmos termos repetidamente porque o assunto está em evolução e eles esperam ver resultados novos. O uso de um worker de serviço permite que a equipe de pesquisa implemente uma lógica detalhada para controlar a vida útil dos resultados da pesquisa armazenados em cache localmente e alcançar o equilíbrio exato de velocidade e atualização que eles acreditam 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 tema, ele quer ir direto à página de pesquisa do Google e começar a pesquisar, sem se preocupar com uma conexão de Internet ativa.
Sem um worker de serviço, visitar a página de pesquisa do Google off-line levava à página de erro de rede padrão do navegador, e os usuários precisavam se lembrar de voltar e tentar novamente quando a conexão retornasse. Com um service worker, é possível exibir 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 exibição mais inteligentes do JavaScript
Outra motivação 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 de JavaScript que fazem sentido quando não há envolvimento do worker de serviço. Por isso, a equipe de pesquisa não queria simplesmente parar de agrupar.
Ao usar a capacidade de um worker de serviço para criar versões e armazenar em cache partes detalhadas de JavaScript no momento de execução, a equipe de pesquisa suspeitou que poderia reduzir a quantidade de rotatividade do cache e garantir que o JavaScript reutilizado no futuro possa ser armazenado em cache de maneira eficiente. A lógica dentro do service worker pode analisar uma solicitação HTTP de saída para um pacote que contém vários módulos JavaScript e a atender agrupando vários módulos armazenados em cache localmente, efetivamente "desempacotando" quando possível. Isso economiza a largura de banda do usuário e melhora a capacidade de resposta geral.
Também há benefícios de desempenho ao usar JavaScript em cache servido por um service worker: no Chrome, uma representação de código de bytes analisada desse JavaScript é armazenada e reutilizada, o que leva a menos trabalho que precisa ser feito no momento da 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 definidas pela equipe. Embora alguns desses desafios sejam específicos do Google Search, muitos deles são aplicáveis a uma ampla gama de sites que podem estar considerando uma implantação de worker de serviço.
Problema: overhead do service worker
O maior desafio, e o único verdadeiro bloqueador para lançar um worker de serviço na Pesquisa Google, foi garantir que ele não fizesse nada que pudesse aumentar a latência percebida pelo usuário. O Google Search leva a performance muito a sério e, no passado, bloqueou o lançamento de novas funcionalidades se elas contribuíssem com dezenas de milissegundos de latência adicional para um determinado grupo 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 as solicitações de navegação para os servidores da Web de back-end, o que exige uma solicitação de rede.
Sem um worker de serviço, essa solicitação de rede acontece imediatamente após a navegação
do usuário. Quando um worker de serviço é registrado, ele sempre precisa ser iniciado
e ter a chance de executar os
gerenciadores de eventos fetch
,
mesmo quando não há chance de que esses gerenciadores de busca façam algo além de ir
para a rede. O tempo necessário para iniciar e executar o código
do worker de serviço é uma sobrecarga adicionada a cada navegação:
Isso coloca a implementação do worker de serviço em uma 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, houve uma ampla distribuição de tempos de inicialização, com alguns dispositivos móveis de baixo custo demorando quase o mesmo tempo para inicializar o service worker que para fazer a solicitação de rede para o HTML da página de resultados.
Solução: use o pré-carregamento de navegação
O recurso mais importante que permitiu à equipe da Pesquisa Google avançar com o lançamento do service worker é o pré-carregamento de navegação. O uso do pré-carregamento de navegação é uma grande vantagem de desempenho para qualquer worker de serviço que precisa usar uma resposta da rede para atender às solicitações de navegação. Ele oferece uma dica para o navegador começar a fazer a solicitação de navegação imediatamente, ao mesmo tempo em que o worker de serviço é iniciado:
Contanto que o tempo que o service worker leva para inicializar seja menor do que o tempo que leva 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 worker de serviço em dispositivos móveis de baixo custo em que o tempo de inicialização do worker de serviço poderia exceder a solicitação de navegação. Como não há uma regra rígida para o que constitui um dispositivo "low-end", eles criaram a heurística de verificar a RAM total instalada no dispositivo. Qualquer coisa com menos de 2 gigabytes de memória caiu na categoria de dispositivos de baixo custo, em que o tempo de inicialização do worker do serviço 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 de pesquisa do Google descubra com antecedência se as tentativas de
armazenamento em cache correm o risco de falha devido a falhas na cota de armazenamento.
Isso deixou a equipe de pesquisa com vários critérios que poderiam ser usados para determinar se um worker de serviço seria usado ou não: se um usuário acessa a página de pesquisa do Google usando um navegador compatível com o pré-carregamento de navegação e tem pelo menos 2 gigabytes de RAM e espaço de armazenamento livre suficiente, um worker de serviço é registrado. Os navegadores ou dispositivos que não atendem a esses critérios não terão um worker de serviço, mas ainda vão ter a mesma experiência de pesquisa do Google.
Um dos benefícios desse registro seletivo é a capacidade de enviar um service worker menor e mais eficiente. A segmentação de navegadores bastante modernos para executar o código do worker de serviço elimina a sobrecarga de transpilação 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 de service worker
Depois que a equipe de Pesquisa realizou experimentos de latência suficientes e teve certeza de que usar o pré-carregamento de navegação oferecia um caminho viável e neutro para a latência ao usar um service worker, alguns problemas práticos começaram a aparecer. Uma dessas questões 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, porque você normalmente usaria um service worker com
o escopo máximo de /
, que pode 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 poderia
assumir o controle de qualquer página hospedada em www.google.com
(ou o equivalente
regional), e há URLs nesse domínio que não têm nada a ver com a
Pesquisa do Google. Um escopo mais razoável e restritivo seria /search
, que pelo menos
eliminaria URLs completamente não relacionados aos resultados da pesquisa.
Infelizmente, mesmo esse caminho de URL /search
é compartilhado entre diferentes versões
dos resultados da Pesquisa Google, com parâmetros de consulta de URL determinando qual tipo específico
de resultado de pesquisa é mostrado. Alguns desses sabores 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 de compras são veiculadas no caminho do URL /search
com parâmetros de consulta
diferentes, mas nenhuma dessas interfaces estava pronta para enviar a própria
experiência de worker de serviço (ainda).
Solução: criar um framework de envio e roteamento
Embora existam algumas propostas que permitem algo mais poderoso do que os prefixos de caminho de URL para determinar os escopos do service worker, a equipe da Pesquisa Google ficou presa na implantação de um service worker que não fazia nada para um subconjunto de páginas que ele controlava.
Para contornar esse problema, a equipe da Pesquisa Google criou um framework de envio e roteamento personalizado que pode ser configurado para verificar critérios como os parâmetros de consulta da página do cliente e usá-los 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 as equipes que compartilham o espaço do URL, como a Pesquisa de imagens e a Pesquisa de compras, insiram a própria lógica de worker de serviço mais adiante, se decidirem implementá-la.
Problema: resultados e métricas personalizadas
Os usuários podem fazer login na Pesquisa do Google usando as Contas do Google, e a experiência dos resultados de pesquisa pode ser personalizada com base nos dados específicos da conta. Os usuários conectados são identificados por cookies de navegador específicos, que é um padrão venerável e amplamente aceito.
Uma desvantagem do uso de cookies do navegador é que eles não são expostos dentro de um worker de serviço, e não há como examinar automaticamente os valores e garantir que eles não mudaram devido ao desligamento de um usuário ou mudança de contas. Há um esforço em andamento para trazer o acesso a cookies para service workers, mas, no momento em que este artigo foi escrito, a abordagem é experimental e não tem suporte amplo.
Uma incompatibilidade entre a visualização do service worker do usuário conectado e o usuário real conectado à interface da Web da Pesquisa Google pode levar a resultados de pesquisa personalizados incorretamente ou a métricas e registros atribuídos incorretamente. Qualquer um desses cenários de falha seria um problema sério para a equipe da Pesquisa do Google.
Solução: enviar cookies usando postMessage
Em vez de esperar que as APIs experimentais sejam lançadas e forneçam acesso direto aos
cookies do navegador em um worker de serviço, a equipe da Pesquisa Google adotou
uma solução provisória: sempre que uma página controlada pelo worker é
carregada, ela lê os cookies relevantes e usa
postMessage()
para enviá-los ao worker.
Em seguida, o worker de serviço verifica o valor do cookie atual em relação ao valor esperado. Se houver uma incompatibilidade, ele vai tomar medidas para limpar todos os dados específicos do usuário do armazenamento e recarregar a página de resultados da pesquisa sem qualquer personalização incorreta.
As etapas específicas que o worker de serviço executa para redefinir as coisas para uma base são específicas dos requisitos da Pesquisa Google, mas a mesma abordagem geral pode ser útil para outros desenvolvedores que lidam com dados personalizados com base em cookies de navegadores.
Problema: experimentos e dinamismo
Como mencionado, a equipe da Pesquisa Google depende muito de experimentos em produção e de testes 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 ativar e desativar experimentos geralmente exige comunicação com o servidor de back-end.
Solução: script de worker de serviço gerado dinamicamente
A solução que a equipe adotou foi usar um script de worker de serviço gerado dinamicamente, personalizado pelo servidor da Web para cada usuário individual, em vez de um único script estático de worker de serviço gerado com antecedência. As informações sobre experimentos que podem afetar o comportamento do worker do serviço ou as solicitações de rede em geral são incluídas diretamente nesses scripts personalizados do worker do serviço. A mudança dos conjuntos de experiências ativas para um usuário é feita por uma combinação de técnicas tradicionais, como cookies do navegador, além de exibir código atualizado no URL do service worker registrado.
O uso de um script de service worker gerado dinamicamente também facilita a criação de uma saída de emergência no caso improvável de que uma implementação de service worker tenha um bug fatal que precisa ser evitado. A resposta dinâmica do worker do servidor pode ser uma implementação sem operação, desativando efetivamente o worker do serviço para alguns ou todos os usuários atuais.
Problema: coordenar atualizações
Um dos desafios mais difíceis de qualquer implantação de service worker do mundo real é encontrar um equilíbrio razoável entre evitar a rede em favor do cache e, ao mesmo tempo, garantir que os usuários atuais recebam atualizações e mudanças importantes logo após a implantação na produção. O equilíbrio certo depende de vários fatores:
- Se o app da Web é um app de página única 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 servidor da Web de back-end.
- Se o usuário médio toleraria usar uma versão um pouco desatualizada do app da Web ou se a atualização é a principal prioridade.
Enquanto testava os service workers, a equipe da Pesquisa Google fez questão de manter os experimentos em várias atualizações programadas do back-end para garantir que as métricas e a experiência do usuário fossem mais próximas do que os usuários voltariam a ver no mundo real.
Solução: equilibre a atualização e a utilização do cache
Depois de testar várias opções de configuração, a equipe do Google Search descobriu que a configuração a seguir oferecia o equilíbrio certo entre atualização e uso 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. O back-end da Web da Pesquisa Google é, como você
pode imaginar, um grande conjunto de servidores distribuídos globalmente que exige o máximo possível de
tempo de atividade. O provisionamento de uma mudança que afete o
conteúdo do script do worker de serviço é feito de forma contínua.
Se um usuário acessar um back-end que foi atualizado e, em seguida, navegar rapidamente para outra página que acessa um back-end que ainda não recebeu o worker de serviço atualizado, ele vai alternar entre as versões várias vezes. Portanto, informar ao navegador para verificar um script atualizado apenas se 25 minutos tiverem se 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 app da Web da Pesquisa Google usem navegações
de estilo app de página única (por exemplo, pela
API History),
na maior parte, a Pesquisa Google é um app da Web tradicional que usa navegações "reais". Isso entra em jogo quando a equipe decide que seria
mais eficiente usar duas opções que aceleram o ciclo de vida da atualização
do service worker:
clients.claim()
e
skipWaiting()
.
Clicar na interface da Pesquisa Google geralmente leva a novos
documentos HTML. A chamada de 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 worker
de serviço atualizado tem a chance de começar a controlar todas as páginas abertas do Google Search
que não estão controladas, após a ativação do worker de serviço.
A abordagem adotada pela Pesquisa Google não é necessariamente uma solução que
funciona para todos. Ela foi o resultado de testes A/B cuidadosos de várias
combinações de opções de veiculação até encontrar o que funciona melhor.
Os desenvolvedores que têm uma infraestrutura de back-end que permite a implantação de atualizações mais
rápidas podem preferir que o navegador verifique um script do worker de serviço 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 vão manter aberto por um longo
período, provavelmente o uso de skipWaiting()
não será a escolha certa para
você. Você
corre o risco de encontrar inconsistências no cache
se permitir que o novo worker de serviço seja ativado enquanto houver clientes
de longa duração.
Pontos-chave
Por padrão, os service workers não são neutros em relação ao desempenho
Adicionar um worker de serviço ao seu app da Web significa inserir um snippet de JavaScript adicional que precisa ser carregado e executado antes que o app da Web receba respostas às solicitações. Se essas respostas vierem de um cache local em vez de da rede, a sobrecarga de execução do worker de serviço será geralmente insignificante em comparação com a vitória de desempenho ao usar o cache primeiro. No entanto, se você souber que o service worker sempre precisa consultar a rede ao processar solicitações de navegação, usar o pré-carregamento de navegação é uma vantagem crucial de desempenho.
Os service workers ainda são uma melhoria progressiva
O suporte a worker de serviço 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 de service workers, como a sincronização em segundo plano e o pré-carregamento de navegação, não são lançados universalmente. A verificação de recursos para o subconjunto específico de recursos que você sabe que precisa e apenas o registro de um worker de serviço quando eles estão presentes ainda é uma abordagem razoável.
Da mesma forma, se você já fez experimentos e sabe que dispositivos de baixo custo têm um desempenho ruim com a sobrecarga adicional de um worker de serviço, também é possível não registrar um worker de serviço nesses cenários.
Continue tratando os service workers como um aprimoramento progressivo que é adicionado a um app da Web 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 worker de serviço teve um impacto positivo ou negativo na experiência dos usuários é testando e medindo os resultados.
As especificidades da configuração de medições significativas dependem do provedor de análise que você usa e de como você normalmente realiza experimentos na configuração de implantação. Uma abordagem, que usa o Google Analytics para coletar métricas, é detalhada em este estudo de caso, com base na experiência de uso de service workers no app da Web do Google I/O.
Não metas
Embora muitos na comunidade de desenvolvimento da Web associem os service workers a Progressive Web Apps, criar uma "PWA da Pesquisa do Google" não era a meta inicial da equipe. No momento, o app da Web da Pesquisa Google não fornece metadados por meio de um manifesto de app da Web, nem incentiva os usuários a passar pelo fluxo de adição à tela inicial. No momento, a equipe de pesquisa está satisfeita com os usuários que acessam o app da Web pelos pontos de entrada tradicionais da Pesquisa Google.
Em vez de tentar transformar a experiência da Pesquisa Google na Web no equivalente do que você esperaria de um aplicativo instalado, o foco no lançamento inicial era melhorar progressivamente o site da Web.
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 contexto usado na escrita deste artigo. Agradecemos especialmente a Philippe Golle, Rajesh Jagannathan e 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 desvantagens da arquitetura atual de worker de serviço. O service worker descrito acima está sendo desativado. À medida que a infraestrutura da Pesquisa da Web do Google evolui, a equipe pode revisar o design do service worker.