Привлечение сервисных работников в Google Поиск

История о том, что именно было отправлено на перевозку, как оценивалось воздействие и на какие компромиссы пришлось пойти.

Опубликовано: 20 июня 2019 г.

Поищите в Google практически любую тему, и вы сразу увидите страницу с содержательными и релевантными результатами. Однако вы, вероятно, не знали, что в определённых сценариях эта страница результатов поиска обслуживается мощной веб-технологией, называемой сервис-воркером .

Внедрение поддержки Service Worker для Google Search без негативного влияния на производительность потребовало привлечения десятков инженеров из разных команд. В этой статье мы расскажем о том, что было выпущено, как измерялась производительность и на какие компромиссы пришлось пойти.

Основные причины для изучения роли работников сферы услуг

Добавление сервис-воркера в веб-приложение, как и любое другое изменение архитектуры сайта, должно осуществляться с четким пониманием поставленных целей. Для команды Google Search существовало несколько ключевых причин, по которым стоило рассмотреть возможность добавления сервис-воркера.

Ограниченное кэширование результатов поиска

Команда Google Search обнаружила, что пользователи часто ищут одни и те же термины несколько раз за короткий промежуток времени. Вместо того чтобы отправлять новый запрос в бэкэнд только для получения, скорее всего, тех же результатов, команда Search решила воспользоваться кэшированием и выполнять эти повторяющиеся запросы локально.

Важность актуальности нельзя недооценивать, и иногда пользователи многократно ищут одни и те же термины, потому что тема постоянно меняется, и они ожидают увидеть свежие результаты. Использование сервис-воркера позволяет команде поиска реализовать детальную логику для управления временем жизни локально кэшированных результатов поиска и достичь именно того баланса между скоростью и актуальностью, который, по их мнению, наилучшим образом отвечает потребностям пользователей.

Значимый опыт работы в офлайн-режиме

Кроме того, команда Google Search хотела обеспечить удобный доступ в офлайн-режиме. Когда пользователь хочет узнать что-то по определенной теме, он хочет сразу перейти на страницу поиска Google и начать поиск, не беспокоясь об активном интернет-соединении.

Без сервис-воркера посещение страницы поиска Google в автономном режиме привело бы к стандартной странице ошибки сети браузера, и пользователям пришлось бы помнить о необходимости вернуться и попробовать снова после восстановления соединения. С сервис-воркером можно отправлять пользовательский HTML-ответ в автономном режиме и позволять пользователям немедленно вводить свой поисковый запрос.

Скриншот интерфейса фоновой повторной попытки.

Результаты станут доступны только после подключения к Интернету, но сервис-воркер позволяет отложить поиск и отправить запрос на серверы Google, как только устройство снова подключится к сети, используя API фоновой синхронизации .

Более интеллектуальное кэширование и развертывание JavaScript

Ещё одной мотивацией была оптимизация кэширования и загрузки модульного кода JavaScript, обеспечивающего работу различных функций на странице результатов поиска. Объединение JavaScript-кода в пакеты имеет ряд преимуществ, которые становятся очевидными, когда не используется сервис-воркер, поэтому команда поиска не хотела просто полностью отказываться от такого объединения.

Используя возможности сервис-воркера по версионированию и кэшированию фрагментов JavaScript-кода во время выполнения, команда разработчиков поисковой системы предположила, что сможет уменьшить объем операций кэширования и обеспечить эффективное кэширование JavaScript-кода, используемого в будущем. Логика внутри их сервис-воркера анализирует исходящий HTTP-запрос на пакет, содержащий несколько модулей JavaScript, и выполняет его, объединяя несколько локально кэшированных модулей — фактически «разбивая» их на части, когда это возможно. Это экономит пропускную способность пользователя и повышает общую скорость отклика.

Использование кэшированного JavaScript, предоставляемого сервис-воркером, также обеспечивает преимущества в производительности: в Chrome проанализированное байт-кодовое представление этого JavaScript сохраняется и используется повторно, что приводит к уменьшению объема работы, необходимой во время выполнения для запуска JavaScript на странице.

Проблемы и решения

Вот несколько препятствий, которые необходимо было преодолеть для достижения поставленных командой целей. Хотя некоторые из этих проблем специфичны для поиска Google, многие из них применимы к широкому кругу сайтов, которые могут рассматривать возможность внедрения сервис-воркеров.

Проблема: накладные расходы на обслуживающий персонал.

Самой большой проблемой и единственным реальным препятствием для запуска сервис-воркера в Google Поиске было обеспечение того, чтобы он не делал ничего, что могло бы увеличить задержку, воспринимаемую пользователями. Google Поиск очень серьезно относится к производительности, и в прошлом блокировал запуск новых функций, если они добавляли даже десятки миллисекунд задержки для определенной группы пользователей.

Когда команда начала собирать данные о производительности во время своих первых экспериментов, стало очевидно, что возникнет проблема. HTML-код, возвращаемый в ответ на запросы навигации для страницы результатов поиска, является динамическим и сильно меняется в зависимости от логики, которая должна выполняться на веб-серверах Поиска. В настоящее время у сервис-воркера нет возможности воспроизвести эту логику и немедленно вернуть кэшированный HTML-код — максимум, что он может сделать, это передавать запросы навигации на бэкэнд-веб-серверы, что требует сетевого запроса.

Без сервис-воркера этот сетевой запрос происходит немедленно после перехода пользователя на сайт. Когда сервис-воркер регистрируется, его всегда необходимо запускать и давать ему возможность выполнить обработчики событий fetch , даже если эти обработчики не будут делать ничего, кроме как обращаться к сети. Время, необходимое для запуска и выполнения кода сервис-воркера, — это чистая дополнительная нагрузка, добавляемая к каждой навигации:

Иллюстрация блокировки запроса на навигацию при запуске программного обеспечения.

Это создает слишком большой недостаток в плане задержки при реализации сервис-воркера, чтобы оправдать какие-либо другие преимущества. Кроме того, команда обнаружила, что, основываясь на измерениях времени загрузки сервис-воркера на реальных устройствах, наблюдалось широкое распределение времени запуска: некоторым мобильным устройствам низкого класса требовалось почти столько же времени для запуска сервис-воркера, сколько для выполнения сетевого запроса на получение HTML-кода страницы результатов.

Решение: использовать предварительную загрузку навигации.

Ключевой особенностью, позволившей команде Google Search продвинуться вперед с запуском сервис-воркера, является предварительная загрузка навигации . Использование предварительной загрузки навигации — это значительное повышение производительности для любого сервис-воркера, которому необходимо использовать ответ из сети для обработки запросов на навигацию. Она дает браузеру подсказку о необходимости немедленно начать отправку запроса на навигацию, одновременно с запуском сервис-воркера:

Иллюстрация запуска программного обеспечения, выполняемого параллельно с запросом на навигацию.

Если время, необходимое для запуска сервис-воркера, меньше времени, необходимого для получения ответа от сети, то никаких дополнительных задержек, связанных с работой сервис-воркера, возникнуть не должно.

Команде разработчиков также нужно было избегать использования сервис-воркера на недорогих мобильных устройствах, где время загрузки сервис-воркера могло превышать время выполнения запроса на навигацию. Поскольку нет четких правил, определяющих, что считается «недорогим» устройством, они придумали эвристический метод проверки общего объема оперативной памяти, установленной на устройстве. Все, что меньше 2 гигабайт памяти, попадало в категорию недорогих устройств, где время запуска сервис-воркера было бы неприемлемым.

Доступное место для хранения — ещё один важный фактор, поскольку полный набор ресурсов, подлежащих кэшированию для дальнейшего использования, может составлять несколько мегабайт. Интерфейс navigator.storage позволяет странице поиска Google заранее определить, существует ли риск сбоя попыток кэширования данных из-за превышения квоты на хранение.

В результате у команды разработчиков поиска появилось несколько критериев, по которым они могли определить, следует ли использовать сервис-воркер: если пользователь заходит на страницу поиска Google, используя браузер, поддерживающий предварительную загрузку навигации, имеющий не менее 2 гигабайт оперативной памяти и достаточно свободного места на диске, то сервис-воркер регистрируется . Браузеры или устройства, не соответствующие этим критериям, не получат сервис-воркер, но они по-прежнему будут видеть тот же интерфейс поиска Google, что и всегда.

Одним из дополнительных преимуществ такой выборочной регистрации является возможность выпуска более компактного и эффективного сервис-воркера. Ориентация кода сервис-воркера на достаточно современные браузеры исключает накладные расходы на транспиляцию и полифилы для более старых браузеров. В результате удалось сократить общий размер реализации сервис-воркера примерно на 8 килобайт несжатого кода JavaScript.

Проблема: области ответственности сервисных работников

После того как команда поиска провела достаточно экспериментов по задержке и убедилась, что использование предварительной загрузки навигации предлагает им жизнеспособный, не вызывающий задержек способ использования сервис-воркера, на первый план вышли некоторые практические проблемы. Одна из таких проблем связана с правилами области действия сервис-воркера. Область действия сервис-воркера определяет, какие страницы он потенциально может контролировать.

Ограничение области действия основано на префиксе пути URL. Для доменов, на которых размещено одно веб-приложение, это не проблема, поскольку обычно достаточно использовать сервис-воркер с максимальной областью действия / , который может управлять любой страницей в рамках домена. Но структура URL в Google Поиске немного сложнее.

Если бы сервис-воркеру был предоставлен максимальный диапазон действия / , он смог бы получить контроль над любой страницей, размещенной под www.google.com (или региональным аналогом), а в этом домене есть URL-адреса, не имеющие никакого отношения к поиску Google. Более разумным и ограничительным диапазоном действия был бы /search , который, по крайней мере, исключил бы URL-адреса, совершенно не связанные с результатами поиска.

К сожалению, даже этот URL-адрес /search используется для разных типов результатов поиска Google, при этом параметры запроса URL определяют, какой именно тип результатов поиска отображается. Некоторые из этих типов используют совершенно иные кодовые базы, чем традиционная страница результатов веб-поиска. Например, поиск изображений и поиск товаров отображаются по URL-адресу /search с разными параметрами запроса, но ни один из этих интерфейсов пока не готов к внедрению собственного сервис-воркера.

Решение: создать систему диспетчеризации и маршрутизации.

Хотя существуют предложения , позволяющие использовать более мощные инструменты, чем префиксы URL-путей, для определения области действия сервис-воркера, команда Google Search столкнулась с проблемой развертывания сервис-воркера, который ничего не делал для подмножества страниц, находящихся под его контролем.

Чтобы обойти эту проблему, команда Google Search разработала собственную систему диспетчеризации и маршрутизации, которую можно было настроить для проверки таких критериев, как параметры запроса клиентской страницы, и использовать их для определения конкретного пути выполнения кода. Вместо жесткого кодирования правил, система была создана гибкой и позволяла командам, использующим одно и то же пространство URL-адресов, например, поиску изображений и поиску товаров, добавлять собственную логику сервис-воркеров в дальнейшем, если они решат ее реализовать.

Проблема: персонализированные результаты и показатели.

Пользователи могут войти в Google Поиск, используя свои учетные записи Google, и результаты поиска могут быть настроены в соответствии с данными их учетной записи. Идентифицированные авторизованные пользователи получают доступ к специальным файлам cookie браузера , что является проверенным и широко распространенным стандартом.

Однако одним из недостатков использования браузерных cookie является то, что они не доступны внутри сервис-воркера, и нет способа автоматически проверить их значения и убедиться, что они не изменились из-за выхода пользователя из системы или смены учетной записи. (В настоящее время предпринимаются попытки обеспечить доступ к cookie для сервис-воркеров , но на момент написания этой статьи данный подход является экспериментальным и не получил широкой поддержки.)

Несоответствие между представлением сервис-воркера о текущем авторизованном пользователе и фактическим пользователем, вошедшим в веб-интерфейс Google Search, может привести к некорректной персонализации результатов поиска или неправильному присвоению метрик и записей в журнал. Любой из этих сценариев сбоев станет серьезной проблемой для команды Google Search.

Решение: отправлять cookie-файлы с помощью postMessage.

Вместо того чтобы ждать запуска экспериментальных API, предоставляющих прямой доступ к куки браузера внутри сервис-воркера, команда Google Search выбрала временное решение: всякий раз, когда загружается страница, управляемая сервис-воркером, страница считывает соответствующие куки и использует postMessage() для их отправки сервис-воркеру.

Затем сервис-воркер проверяет текущее значение cookie на соответствие ожидаемому значению, и если обнаруживается несоответствие, он предпринимает шаги для удаления из хранилища всех данных, относящихся к конкретному пользователю, и перезагружает страницу результатов поиска без некорректной персонализации.

Конкретные шаги, которые предпринимает сервис-воркер для сброса настроек до базового уровня, зависят от требований Google Search, но тот же общий подход может быть полезен и другим разработчикам, работающим с персонализированными данными, основанными на файлах cookie браузера.

Проблема: эксперименты и динамика

Как уже упоминалось, команда Google Search в значительной степени полагается на проведение экспериментов в производственной среде и тестирование влияния нового кода и функций на реальные условия, прежде чем включать их по умолчанию. Это может быть непростой задачей для статического сервис-воркера, который в значительной степени полагается на кэшированные данные, поскольку включение и выключение пользователей в экспериментах часто требует взаимодействия с бэкэнд-сервером.

Решение: динамически генерируемый скрипт сервис-воркера.

Команда выбрала решение использовать динамически генерируемый скрипт сервисного работника, настраиваемый веб-сервером для каждого отдельного пользователя, вместо единого статического скрипта сервисного работника, генерируемого заранее. Информация об экспериментах, которые могут повлиять на поведение сервисного работника или сетевые запросы в целом, включается непосредственно в эти настраиваемые скрипты сервисного работника. Изменение набора активных действий для пользователя осуществляется с помощью сочетания традиционных методов, таких как cookie-файлы браузера, а также путем предоставления обновленного кода в зарегистрированном URL-адресе сервисного работника.

Использование динамически генерируемого скрипта сервис-воркера также упрощает обеспечение запасного варианта в маловероятном случае, если в реализации сервис-воркера обнаружится критическая ошибка, которую необходимо предотвратить. Динамический ответ сервер-воркера может представлять собой реализацию, не выполняющую никаких действий , фактически отключая сервис-воркер для некоторых или всех текущих пользователей.

Проблема: координация обновлений

Одна из самых сложных задач, стоящих перед любым реальным развертыванием сервис-воркеров, — это поиск разумного компромисса между отказом от использования сети в пользу кэширования и одновременно обеспечением того, чтобы существующие пользователи получали критически важные обновления и изменения вскоре после их развертывания в производственной среде. Правильный баланс зависит от множества факторов:

  • Независимо от того, является ли ваше веб-приложение долгоживущим одностраничным приложением , которое пользователь держит открытым неограниченное время, не переходя на новые страницы.
  • Каков график развертывания обновлений для вашего серверного веб-сервера.
  • Вопрос в том, будет ли среднестатистический пользователь мириться с использованием слегка устаревшей версии вашего веб-приложения, или же актуальность является для него первостепенной задачей.

В ходе экспериментов с сервисными работниками команда Google Search следила за тем, чтобы эксперименты продолжались независимо от запланированных обновлений бэкэнда, дабы показатели и пользовательский опыт максимально точно соответствовали тому, что пользователи увидят в реальной жизни.

Решение: сбалансировать актуальность данных и использование кэша.

После тестирования ряда различных вариантов конфигурации команда Google Search обнаружила, что следующая настройка обеспечивает оптимальный баланс между актуальностью и использованием кэша.

URL скрипта сервис-воркера отправляется с заголовком ответа Cache-Control: private, max-age=1500 (1500 секунд или 25 минут) и регистрируется с параметром updateViaCache, установленным в значение 'all', чтобы гарантировать соблюдение этого заголовка. Веб-бэкэнд Google Search, как вы можете себе представить, представляет собой большой, глобально распределенный набор серверов, требующий максимально возможной бесперебойной работы. Внесение изменений, влияющих на содержимое скрипта сервис-воркера, осуществляется поэтапно.

Если пользователь обращается к обновленной версии бэкэнда, а затем быстро переходит на другую страницу, которая обращается к бэкэнду, еще не получившему обновленный сервис-воркер, он будет многократно переключаться между версиями. Поэтому указание браузеру проверять наличие обновленного скрипта только через 25 минут после последней проверки не имеет существенных недостатков. Преимущество такого подхода заключается в значительном сокращении трафика, поступающего на конечную точку, которая динамически генерирует скрипт сервис-воркера.

Кроме того, в HTTP-ответе скрипта сервис-воркера устанавливается заголовок ETag, гарантирующий, что при проверке обновлений по истечении 25 минут сервер сможет эффективно ответить HTTP-ответом 304, если за это время не было никаких обновлений для развернутого сервис-воркера.

Хотя некоторые взаимодействия в веб-приложении Google Поиск используют навигацию в стиле одностраничных приложений (например, через History API ), по большей части Google Поиск — это традиционное веб-приложение, использующее «настоящую» навигацию. Это стало важным моментом, когда команда решила использовать два варианта, ускоряющих жизненный цикл обновления сервис-воркера: clients.claim() и skipWaiting() . Нажатия на элементы интерфейса Google Поиск обычно приводят к переходу на новые HTML-документы. Вызов skipWaiting гарантирует, что обновленный сервис-воркер получит возможность обработать эти новые запросы навигации сразу после установки. Аналогично, вызов clients.claim() означает, что обновленный сервис-воркер получает возможность начать управлять любыми открытыми страницами Google Поиска, которые не контролируются, после активации сервис-воркера.

Подход, выбранный Google Search, не обязательно подходит всем — он стал результатом тщательного A/B-тестирования различных комбинаций параметров обслуживания, пока не был найден оптимальный вариант. Разработчики, чья бэкэнд-инфраструктура позволяет им быстрее развертывать обновления, могут предпочесть, чтобы браузер проверял наличие обновленного скрипта сервис-воркера как можно чаще, всегда игнорируя HTTP-кэш . Если вы создаете одностраничное приложение, которое пользователи могут держать открытым в течение длительного времени, использование skipWaiting() вероятно, не лучший выбор — вы рискуете столкнуться с несоответствиями кэша, если позволите новому сервис-воркеру активироваться, пока есть долгоживущие клиенты.

Основные выводы

По умолчанию сервисные работники не являются нейтральными по отношению к производительности.

Добавление сервис-воркера в ваше веб-приложение означает вставку дополнительного фрагмента JavaScript, который необходимо загрузить и выполнить до того, как ваше веб-приложение получит ответы на свои запросы. Если эти ответы поступают из локального кэша, а не из сети, то накладные расходы на запуск сервис-воркера обычно незначительны по сравнению с выигрышем в производительности от использования кэша в первую очередь. Но если вы знаете, что ваш сервис-воркер всегда должен обращаться к сети при обработке запросов на навигацию, использование предварительной загрузки навигации становится решающим фактором повышения производительности.

Работники сферы услуг (по-прежнему!) являются прогрессивным улучшением.

Сегодня ситуация с поддержкой сервис-воркеров гораздо лучше, чем даже год назад. Все современные браузеры теперь поддерживают сервис-воркеры хотя бы частично, но, к сожалению, некоторые продвинутые функции сервис-воркеров — такие как фоновая синхронизация и предварительная загрузка навигации — не внедрены повсеместно. Проверка наличия необходимых вам функций и регистрация сервис-воркера только при их наличии по-прежнему остается разумным подходом.

Аналогично, если вы проводили эксперименты на практике и знаете, что устройства низкого класса показывают низкую производительность из-за дополнительных затрат на использование сервис-воркера, вы можете воздержаться от регистрации сервис-воркера и в таких сценариях.

Следует и дальше рассматривать сервис-воркеры как постепенное улучшение , добавляемое в веб-приложение после выполнения всех необходимых условий и положительного влияния сервис-воркера на пользовательский опыт и общую скорость загрузки.

Измеряйте всё

Единственный способ выяснить, оказало ли внедрение сервисного работника положительное или отрицательное влияние на пользовательский опыт, — это поэкспериментировать и измерить результаты.

Специфика настройки значимых измерений зависит от используемого поставщика аналитических услуг и от того, как вы обычно проводите эксперименты в своей системе развертывания. Один из подходов, использующий Google Analytics для сбора метрик, подробно описан в этом тематическом исследовании, основанном на опыте использования сервисных воркеров в веб-приложении Google I/O.

Неголы

Хотя многие в сообществе веб-разработчиков ассоциируют сервис-воркеры с прогрессивными веб-приложениями (PWA) , создание «PWA для поиска Google» изначально не было целью команды. Веб-приложение «Поиск Google» не предоставляет метаданные в манифесте веб-приложения и не побуждает пользователей проходить процедуру добавления на главный экран . Команда поиска довольна тем, что пользователи заходят в их веб-приложение через классические точки входа в поиск Google.

Вместо того чтобы пытаться превратить веб-интерфейс Google Search в аналог установленного приложения, в первоначальной версии основное внимание было уделено постепенному улучшению существующего веб-сайта.

Благодарности

Благодарим всю команду разработчиков веб-сайта Google Search за их работу над реализацией сервис-воркера и за предоставленные материалы, которые легли в основу написания этого текста. Особая благодарность Филиппу Голле, Раджешу Джаганнатхану, Р. Самуэлю Клатчко, Энди Мартоне, Леонардо Пенья, Рэйчел Ширер, Грегу Терроно и Клэю Вуламу.

Обновление (октябрь 2021 г.): После первоначальной публикации этой статьи команда Google Search пересмотрела преимущества и недостатки своей текущей архитектуры сервис-воркеров. Описанный выше сервис-воркер выводится из эксплуатации. По мере развития веб-инфраструктуры Google Search команда может вернуться к своему проекту сервис-воркеров.