Хабрахабр

Как ускорить мобильный поиск в два раза. Лекция Яндекса

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Вот простой пример: телефон, снимаем с него latency до Яндекс, запускаем команду ping и смотрим, чему она равна. Перегрузки, нестабильные сигналы, физические помехи. Начинаем в это время загружать канал, просто скачиваем страничку, latency вырастает почти в 10 раз, до 6 секунд. Видим, что в обычной ситуации 0,5 с одно сетевое вхождение. Пять сетевых вхождений, 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. Результаты поиска получили, дальше их прогружаем. Фулбэчимся на Zopfli для картинок, оптимизируем картинки, если браузер умеет WebP, то его используем. Brotli — где это возможно. Умеет 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-соединениям. Видно, что график постоянно растет, то есть оптимизации внедряются постоянно, много команд в Яндексе над этим работают. Как мы видим, за год нам удалось ускорить первую отрисовку практически в два раза.

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

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»