Продвинутая оптимизация сайта

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

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

Основы

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

Когда мы запрашиваем какой-либо ресурс с сайта, в первую очередь у нас делается dns lookup, то есть клиент делает запрос к своему DNS серверу, который ему должен преобразовать доменное имя сайта в IP-адрес. Тут особо оптимизировать мы ничего не сможем. Просто пользуйтесь DNS серверами, которые находятся близко к региону, откуда планируется львиная доля трафика.

После того, как IP-адрес получен, клиент пытается установить соединение с сервером. Процесс установки TCP соединения начинается с так называемого “three way handshake”, который состоит из трех шагов, когда клиент посылает серверу флаг SYN, сервер ему отвечает, после чего соединение считается установленным и клиент уже посылает флаг ACK. В общем, суть в том, что пакеты летят от клиента до вашего сервера, а скорость света, она хоть и большая, но имеет свой предел. Именно поэтому, например, полет пакета от моего ноутбука до сервера Яндекса занимает целых 20 миллисекунд. Это время называется Round Trip Time, то есть время между тем, как был послан запрос и получен на него ответ. Так вот, получается, установка TCP соединения у нас занимает один RTT. На этом с вводной информацией закончим и перейдем к практике.

Keep-alive

В первую очередь, убедитесь в том, что ваш сервер поддерживает keep-alive. При загрузке вашего сайта погружается куча файлов (стили CSS, скрипты JavaScript, шрифты, картинки). Если у вас не включен keep-alive, загрузка каждого файла оборачивается установкой нового TCP-соединения. Таким образом, при необходимости загрузки 20 файлов мы уже теряем 19 RTT времени. Если взять при хорошем раскладе 1 RTT равным 20 мс, то это уже потеря 380 мс времени. Почти полсекунды вникуда.

Gzip

Даже Google PageSpeed ругается, если у вас отключено сжатие. Тут все очень просто, gzip позволяет сжимать ваши CSS и JS файлы в несколько раз и экономить на времени передачи этих данных.

Снова немного углубимся в теорию и принципы работы протокола TCP, чтобы понять, почему это важно. Во время установки соединения клиент и сервер договариваются о так называемом MTU (Maximum transmission unit). Этот параметр определяет максимальный объем данных, которые могут быть переданы в одном пакете. Полет одного пакета у нас занимает один RTT. Обычно, значение MTU равно 1500. Это значит, что для передачи файла размером 100 килобайт (типичный размер для файла CSS) нам потребуется 100 * 1024 / 1500 = 68,2 = 69 пакетов. Вся соль заключается в том, что если бы файл весил 99,6 кб, то у нас бы ушло всего 68 пакетов вместо 69 и мы бы сэкономили 1 RTT. Именно поэтому следует всегда стремиться к тому, чтобы получить минимальный размер передаваемых данных.

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

Минификация

Порой, минификаторы позволяют очень сильно экономить на размере ваших скриптов и файлов стилей, особенно если в них много комментариев. Инструментов для минификации множество. Я пользуюсь YUI Compressor. Если ваше приложение работает на фреймворке Ruby on Rails, то там уже есть свой UglifyJS.

На практике, если у вас уже включен gzip, минификация дает очень небольшое уменьшение размера файла. Но по причине, описанной в предыдущем пункте нам нужно стремиться в минимальному размеру файла, чтобы не было попадалова на лишний RTT.

Сжатие картинок

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

Использование спрайтов

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

Склейка скриптов и таблиц стилей

По той же причине имеет смысл склеивать ваши скрипты JavsScript и стили CSS в один файл. Тем более, cat есть почти у каждого, кто ушел от Windows.

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

Отдельный домен для статики

Теперь давайте вспомним о такой штуке, как cookies. Такая информация, которую сервер хранит на клиенте и которая каждый раз посылается вместе с заголовками на сервер. А теперь еще давайте вспомним, что куки посылаются не только при запросе страницы, а при запросе любого URL с вашего сервера. Это значит, что на каждом файле из-за лишних 200 байт вы можете получить лишний RTT. То есть при запросе 20 файлов вы реально можете потерять лишние 10 RTT, а это уже не так мало.

Вынося статику на отдельный домен или поддомен вы можете еще немного ускорить загрузку вашего сайта.

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

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

Однако, разнос статики по доменам не всегда может оказаться лучшим решением. Все дело в том, в реализации протокола TCP присутствует такое понятие, как размер окна (window size). Сам протокол гарантирует передачу данных и для этого ему необходимо каждый раз после отправки пакета опрашивать вторую сторону о том, получены ли данные. Такой способ передачи является довольно затратным и медленным, поэтому появилось понятие как размер окна, которое определяет размер данных, которые могут быть переданы без подтверждения. В начале соединения размер окна установлен в минимальное значение. После каждого успешно отправленного пакета окно увеличивается в два раза. В последнее время даже используется квадратичная зависимость. Если вдруг пакет теряется, размер окна сбрасывается опять в минимальное значение. Таким образом соединение разгоняется. Имеено по этой причине в некоторых случая раздача статики с одного домена может быть эффективнее, чем с разных, за счет того, что данные будут передаваться по уже разогнанному соединению.

Использование CDN

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

HTML Inline

Иногда имеет смысл вставить ваш JavaScript или CSS в прямо в тело HTML-документа (inline). Тут уже нужно руководствоваться здравым смыслом. Однако, стоит помнить о том, что inline нельзя закэшировать на клиенте.

Использование localStorage

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

Кэширование на клиенте

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

Загрузка скриптов после контента

Здесь все просто. Размещайте все теги <script> перед самым </body>. Часто можно встретить эти теги в секции <head>. Такие проделки могут блокировать загрузку контента страницы до тех пор, пока скрипты не будут загружены. Хотя сами скрипты являются далеко не самым важным на странице и могут быть загружены после того, как пользователь увидел саму страницу.

Асинхронная загрузка стилей

Часто бывает так, что у сайта есть огромный файл со стилями. Но лишь небольшая часть из этих стилей является базовой и влияет на отображение основной части страницы. Именно эта часть CSS должна подгружаться в секции <head>. Остальные стили мы можем погрузить после полной загрузки страницы. Для этого можно воспользоваться таким хаком на JavaScript, который необходимо разместить перед </body>.

<script>
    var link = document.createElement('link');
    link.setAttribute('rel', 'stylesheet');
    link.setAttribute('href', '/css/highlight.min.css');
    document.getElementsByTagName('head')[0].appendChild(link);
</script>

Если у вас подключен jQuery, то там код будет еще проще.

Постоянные соединения

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

Немного хардкора

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

Например, есть еще такое решение, как Point of Presence. Его суть заключается в том, что рядом с пользователями устанавливается прокси-сервер. Этот прокси держит постоянное соединение с фронтэндами, которые могут находиться на другом материке, и имеет максимальный window size. Таким образом, клиент подключается к прокси-серверу по неразогнанному соединению, однако из-за того, что они находятся рядом, мы получаем хорошую скорость передачи данных между ними. От прокси данные летят на другой материк уже по разогнанному соединению. Таким образом можно получить хороший профит в скорости загрузки нашего сайта для клиентов с других материков.

Заключение

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