На телефоне веб-страницы часто загружаются дольше, чем на десктопе. Разработчик Иван Хватов рассказывает о причинах отставания и о том, как с ним справляться. Лекция состоит из нескольких частей: первая — про основные этапы загрузки страницы на мобильных устройствах, вторая — про техники, которые мы применяем для ускорения загрузки, третья — про наш метод адаптации верстки под разную скорость.


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

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



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

Чтобы понять причины, давайте рассмотрим особенность мобильного веба.



Мобильный интернет почти на 100% беспроводной: сотовый или Wi-Fi. У любого соединения есть характеристики: задержка, время прохождения одного пакета до сервера и назад, команда ping. И — ширина канала: то, сколько данных в единицу времени пропускает соединение.

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

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



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



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

Отдельно замечу, что установка безопасного соединения тоже занимает значимое время, это красная зона.

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



Что же происходит с установкой соединения? В самом худшем случае браузер резолвит DNS, получает IP-адрес, делает еще одно сетевое входное, устанавливает TCP соединение, дальше устанавливает безопасное соединение. С ним, как правило, чуть посложнее. Как минимум нужно сделать два запроса. Про это подробнее расскажем, два сетевых вхождения. И задаем запрос.

В итоге, чтобы просто получить первые данные, нужно выполнить пять сетевых вхождений. Давайте посмотрим на реальный мобильный интернет в России, чему равно одно сетевое вхождение по времени.



Мы видим, что у 50% пользователей в рунете, в принципе, все хорошо с latency: 77 мс, пять сетевых вхождений, пользователь практически не замечает этот момент. Но у второй части пользователей latency сильно растет. И уже к 10 самым медленным одно сетевое вхождение равняется почти 2 с. То есть 10% пользователей Яндекса будут ожидать полной установки соединения практически 10 с, это очень долго.

Почему такой хвост, почему так latency растет?



Две причины. Во-первых, технологии сетей, если посмотрим на 2G и на 3G, то по типу технологии сети, медианная latency, у них это не по паспорту, не по технологии, а реальные цифры, что мы снимаем, latency очень плохое. С Wi-Fi и 4G все более-менее нормально. Но есть нюансы.



Перегрузки, нестабильные сигналы, физические помехи. Вот простой пример: телефон, снимаем с него latency до Яндекс, запускаем команду ping и смотрим, чему она равна. Видим, что в обычной ситуации 0,5 с одно сетевое вхождение. Начинаем в это время загружать канал, просто скачиваем страничку, latency вырастает почти в 10 раз, до 6 секунд. Понятно, если в это время, когда канал загружен, мы начнем делать какие-то блокирующие вызовы, необходимые для отрисовки, пользователь никогда их не дождется. Пять сетевых вхождений, 25 секунд, никто столько ждать не будет.

Что с этим делать? Давайте оптимизировать.



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



Как можно делать соединение? Это, казалось бы, простой момент. Смотрим, чтобы на сервере был настроен keepalive. Но есть нюансы. Если приложение помещается в плохую сеть, то часто начинает соединение рваться по тайм-ауту. В обычных условиях мы это не замечаем, на медленных сетях это становится проблемой, соединение рвется по тайм-ауту, и браузеру нужно пересоздавать соединение. Поэтому анализируем тайм-ауты, какие есть на уровне приложения, и настраиваем их корректно.

Второе — преконнект. Делаем установку соединения еще до того, как это соединение нужно. Когда придет время и пользователь запросит страничку, у нас уже будет готовое соединение, и ему не нужно будет долго ждать.

Преконнект — это стандартная директива, ее можно указывать прямо в HTML, браузер ее понимает. Большинство.

Мы в Яндексе проанализировали, как у нас запросы ходят, нашли сервис, который показывает поисковые подсказки. Поняли, что он в самом начале работы, когда еще нет готовых соединений, делает это через холодное соединение. Немножко поменяли архитектуру, сделали так, чтобы он переиспользовал уже соединение у другого сервиса, и количество показов этого сервиса выросло практически на 5%.



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

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

Вторая особенность в наших логах — было очень много редиректов с HTTP на HTTPS. Пользователь переходит на HTTP, мы его редиректим на HTTPS, он снова ждет, и еще устанавливает безопасное соединение.

Чтобы этого не было, есть технология HSTS — можно в ответе сервера один раз сказать, что наш сайт работает только по HTTPS, и следующий раз, когда мы будем делать заход по HTTP, браузер автоматически сделает внутренние редиректы, сразу сделает запрос на HTTPS.



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

Помимо того, что здесь два сетевых вхождения, клиент и сервер еще какое-то процессорное время тратят на вычисление. Это тоже плохо и тоже медленно.

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

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

Поэтому на помощь приходит вторая оптимизация, называется TLS False Start.


(Ссылка — прим. ред.)

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

В принципе, ничего не мешает, и эта методика называется TLS False Start, когда клиент получает первые данные от сервера, он сразу их шифрует, и вместе с прохождением установки соединения отправляет запрос.

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

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

Подробнее о том, как настроить HTTPS-сервис, можете почитать в посте нашей службы безопасности на Хабре, там про это все есть.

После того, как мы включили оптимизацию TLS False Start, тикет давно был включен, и мы ускорили установку безопасного соединения в среднем на 13% на мобильных. Она очень эффективна.



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

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

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

Мы попробовали выключить OSCP, подрезали сертификат, запустили эксперимент и увидели, что у нас TLS хэндшейк еще на 7% ускоряется. Решили оставить эту оптимизацию в продакшене. Заметили, что у нас деградировала установка соединения в Opera, которая смотрит на OCSP response, но через какое-то время, когда нагрелся кэш в браузере, там OSCP response кэшируется, который Opera не из ответа сервиса достает OCSP, а ходит в отдельный сервис блокирующий и проверяет, не отозван ли сертификат для yandex.ru. Если не отозван, то ответ кэшируется какое-то время, и следующий раз уже такого хождения нет.

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

Хорошо, установили соединение, дальше загружаем контент. Сервер получил запрос, в нашем случае это поисковый запрос, поиск работает не моментально. Там много серверов, много сетевых вхождений, ранжирование, это все занимает время, мы над этим активно работаем, тоже оптимизируем, но тем не менее. Как можно эту ситуацию обыграть?

У нас есть поисковая стрелка, которая никак не зависит от результатов поиска. Мы можем эту поисковую стрелку начать отправлять пользователю еще до того, как мы получили выдачу в результате поиска. Ставим Chunked-Encoding в HTTP, отправляем поисковую стрелку. Во-первых, пользователь видит, что у него страничка загружается, он не просто смотрит на белый экран. Во-вторых, мы еще и ускоряемся. Когда мы передаем эту поисковую стрелку, мы разогреваем TCP-соединение, и следующий раз, когда у нас готовы результаты поиска, они быстрее приезжают. Плюс эта поисковая стрелка может JavaScript’ом что-то догрузить и занять соединение.



Результаты поиска получили, дальше их прогружаем. Здесь банальная оптимизация, что как можно сильнее надо ужать данные, поэтому мы используем Brotli, Zopfli. Brotli — где это возможно. Фулбэчимся на Zopfli для картинок, оптимизируем картинки, если браузер умеет WebP, то его используем. Умеет SVG — используем SVG.

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

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



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

Когда мы включили WebP на карт-сервисе, поиск по картинке, мы заметили, что у нас на 20% увеличилось количество полных загруженных выдач. Пользователи стали дожидаться, когда у них страничка полностью загружена. Мы срезали трафик на 14%.

Из минусов, у нас снизился хэш-хит. Мы добавили серверов, эту проблему порешали.

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

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

Во-вторых, сделать супер-верстку, которая будет работать в любых сетях, на любых устройствах. Есть несколько способов научиться определять скорость соединения на сервере. Мы используем данные ядра Linux, данные TCP_INFO, смотрим на время установки TLS-соединения, и если клиенты очень умные и сами нам говорят, что они в плохой сети, то мы используем и эти данные.



Прекрасно, что данные TCP_INFO можно использовать прямо в nginx, одной строчкой кода включить конфиг, и метрики скорости будут приезжать на бэкенды.



SSL, замеряем время, когда мы отправляем пакет и получаем пакет от пользователя. Этот интервал считаем временем одного сетевого вхождения.



Все эти факторы используем для того, чтобы определить скорость соединения.



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

После того, как мы запустили эксперимент на Яндексе, мы увидели, что на 2,2% увеличивается количество показов поиска, количество загрузок поисковой выдачи. Это очень большой результат. Это значит, что на 2,2% больше наших пользователей смогли решить свои задачи в медленных сетях.



Посмотрим на график скорости отрисовки выдачи за год. Здесь нормировано к октябрю. Видно, что график постоянно растет, то есть оптимизации внедряются постоянно, много команд в Яндексе над этим работают. Но есть три этапа: в первом мы как раз внедрили легкую поисковую выдачу, начальное включение, во втором мы улучшили алгоритм определения медленных соединений, в третьем — внедрили оптимизацию по TLS-соединениям. Как мы видим, за год нам удалось ускорить первую отрисовку практически в два раза.

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

Комментарии (2)


  1. fapsi
    20.05.2018 14:35
    +7

    Господа из Яндекса, если я со смартфона на минутку заглядываю на сайт-переводчик Яндекса, то почему я сначала должен отказаться от предложения установить мобильное приложение, а потом только воспользоваться сервисом по переводу? Это такой вид ускорения?


  1. wailwolf
    20.05.2018 23:09

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