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

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

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

Для этого мне потребовались фиктивные ответы на динамические запросы и согласованная сетевая задержка для статических ресурсов. Я решил создать среду с неограниченной пропускной способностью и низкой задержкой, используя PhantomJS. Хотя PhantomJS не является браузером, используемым нашими пользователями, разумно ожидать, что улучшения, наблюдаемые в PhantomJS, должны привести к улучшениям в обычном браузере, и это все, что я хотел знать.

Мне нужен был сценарий, который я мог бы запустить, который сообщал бы мне, сколько миллисекунд требуется, чтобы сайт стал доступен потребителю. Чтобы включить это, я создал следующую настройку с помощью Docker:

Я создал bootstrap.htmlpage, которая загружает Klarna Checkout так же, как и в рабочей среде. Затем я создал образ докера NGINX, содержащий локально скомпилированную ветвь кода, и обновил bootstrap.html, чтобы он указывал на этот контейнер (с именем nginx.service).

Затем я написал приведенный ниже сценарий для выполнения PhantomJS, используя onResourceRequested() для перезаписи любых URL-адресов CDN для загрузки с локального NGINX. Чтобы рассчитать время загрузки, я воспользовался событиями, которые мы отправляем на этапе загрузки. Мне понадобилось всего два события:

  • bootstrap - означает начало загрузки пользователя.
  • ready - означает, что пользователь может начать вводить детали.

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

Мы немного посмотрели и увидели manifest.appcache в журнале загружаемых файлов и заметили, что размер файла у нас разный. Я слышал, что у нас есть «файл манифеста», но на самом деле мало что знал о нем и решил, что он в значительной степени необязательный, поэтому я просто сказал: «Давайте посмотрим, что произойдет, если мы его удалим». Бум, я получаю те же результаты, что и мой коллега. Что же это за манифест кеша приложения?

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

Итак, почему мы его использовали? Оказывается, кеш приложения был добавлен некоторое время назад, чтобы повысить производительность Klarna Checkout для интеграции собственных приложений. Однако на самом деле в этом не было необходимости, поскольку для нашего варианта использования стандартные методы HTTP-кеширования уже дали нам то, что мы хотели - кэшированные ресурсы, загружаемые без подключения к сети. (Бесстыдный плагин - просмотрите мой доклад о HTTP-кешировании, чтобы освежиться.)

Далее - как этого не заметили? Конечно, один из нас заметил бы, что некоторые файлы загружаются слишком быстро. Мой инструмент тестирования PhantomJS показал нам, что есть проблема, и пришло время открыть Chrome, чтобы понять больше.

Оказывается, Chrome мало что может рассказать об офлайн-загрузках. Вы можете узнать, какие офлайн-приложения сохранил Chrome, посмотрев на chrome://appcache-internals. Однако у меня все еще были вопросы о том, как и когда эти файлы были загружены и как загрузки влияют на потенциально одновременные запросы одних и тех же ресурсов из самого HTML. И тут мне пригодился мой новый лучший друг chrome://net-internals.

Поскольку сетевой трафик кэша приложений отсутствует на вкладке Сеть, chrome://net-internals#events - единственное место, где можно увидеть этот трафик. Основываясь в основном на старом отчете об ошибке в Chrome, я обнаружил, что могу:

  1. Закройте все вкладки Chrome, чтобы ограничить шум в потоке событий net-internals.
  2. Откройте окно в режиме инкогнито
  3. Перейдите к chrome://net-internals/#capture, нажмите "Сброс".
  4. Откройте новую вкладку и перейдите на мой сайт
  5. Посмотрите сетевые события в chrome://net-internals/#events

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

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

Когда мы запустили изменения в производство, они оказали драматическое влияние, особенно на Chrome Mobile, сократив время загрузки на 25%. В других браузерах тоже было улучшение, но более скромное.

Итак, что я узнал?

  • Проверка ваших предположений с помощью сравнительного анализа может привести к удивительным результатам и стоит затраченных усилий.
  • Никогда не используйте кеш приложения (или сервис-воркеров), если вам действительно не нужны автономные функции. Вместо этого верьте в заголовки HTTP-кеша. (Даже если вам действительно нужны автономные функции, вам следует сначала прочитать эту статью.)
  • chrome://net-internals - чрезвычайно мощная вкладка, которая может помочь ответить на множество вопросов, которые могут возникнуть у вас о том, как работает сеть в браузере, на уровне немного более глубоком, чем позволяет вкладка Сеть. Некоторые пояснения по этому поводу вы можете найти здесь.

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

А ты? Кеш приложений сгорел? Как вы тестируете свои приложения, чтобы получить представление о производительности перед выпуском?

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

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