Решил я недавно на примере одного проекта узнать, насколько сильно влияет на скорость загрузки сайта domain sharding. Напомню, суть этой оптимизации в том, что статические файлы грузятся с разных доменов (которые, впрочем, могут указывать на один и тот же сервер), и это позволяет обходить ограничение браузеров на количество одновременных подключений к одному домену. Интуитивно кажется, что в случае большого количества мелких файлов это должно существенно ускорить загрузку сайта в целом. Проверим, так ли это на самом деле.
Вкратце опишу ситуацию: имеется довольно длинный одностраничный сайт, в процессе загрузки совершается чуть больше сотни запросов на загрузку статики (css, js, шрифты, изображения). Сайт написан с использованием Ruby on Rails 4.1.12, в качестве веб-сервера — puma-2.15.3, nginx отдаёт статику и смотрит на пуму. Запущено это всё на дроплете Digital Ocean в локации Frankfurt 1. И, имея такие начальные данные, нам надо перенести запросы на статику с доменов вида example.com на домены вида assets%{i}.example.com.
Прежде всего надо настроить отдачу статики с этих адресов. Для этого достаточно настроить соответствующие DNS-записи (у меня было просто установлена запись для *.example.com, в моём случае этого было достаточно), а затем изменить настройки nginx'а (в директиве server_name стоит регулярка, отлавливающая хосты вида assets0.example.com и assets0.example.ru, т.к. в моём случае сайт доступен с двух разных адресов):
Затем необходимо изменить генерацию путей к статике на стороне приложения. В рельсах это элементарно: достаточно в config/production.rb добавить строчку
Магия рельс — это, конечно, хорошо, но в моём случае она не сработала бы из-за необходимости генерировать разные адреса при посещении сайта с разных доменов. Впрочем, настраивается это не сильно сложнее. Также я решил настроить возможность опционального включения/отключения domain sharding на сайте без необходимости изменения кода приложения. Проще всего это было сделать с использованием переменных окружения:
Определение порядкового номера домена выглядит именно таким образом (source.hash % 4), чтобы при перезагрузке страницы (ну или при сбросе кэша сервера) для каждого файла ссылка оставалась неизменной. Это позволит эффективнее использовать кэш браузера. Также для меня остаётся загадкой, зачем нужна проверка на существование request, но в доках было написано именно так, и я не стал копаться глубже.
Запускаю
С грустной мыслью о том, что не всегда всё работает из коробки, рефлекторно полез узнавать что-нибудь про «Cross-Origin Resource sharing policy rails fonts». Увидел упоминание гема font_assets, в README нашёл строку «Sets Access-Control-Allow-Origin response headers for font assets» и решил, что это как раз то, что мне нужно.
Моя ошибка была в том, что нужно было сначала подумать. Тогда бы я сразу понял, что шрифты, как и остальная статика, на боевом сервере отдаются nginx'ом, который ни о каких гемах знать не знает. На деле же вышел небольшой квест: после подключения font_assets сломалось всё; нашёл, почему сломалось, поправил исходники, заработало; сделал форк, прописал его в Gemfile; обновил версию на продакшене; понял, что надо было сначала подумать; откатил версию, удалил форк.
Собственно, исправление ситуации на продакшене было простым: небольшая правка секции location решает проблему:
В общем, на этом собственно настройка закончилась и я начал замеры.
Замерял так: открыл в инспекторе хрома вкладку Network, в фильтре ставил domain:*.example.com / domain:example.com, и обновлял страницу. Не самый высокотехнологичный способ, но позволяет отслеживать не только время загрузки, но и её характер. (На скриншотах показаны только нижние части графиков).
Со включенным кэшем итоговое время довольно сильно прыгало вокруг средних значений, но обычно с domain sharding загрузка происходила на ≈0.2-0.4 секунд быстрее. В FF окончание загрузки происходило примерно одинаково, но с включённым шардингом бОльшая часть файлов становилась доступной раньше. Также на графиках наглядно видны ограничения браузеров на количество одновременных соединений: максимум 6 к одному хосту, максимум 10 в целом.
С отключенным кэшем картина сглаживалась, но всё равно c шардингом было немного быстрее. Не совсем понял, почему, но при включении ограничения скорости до 750 kB/s без шардинга работало чуточку быстрее.
Подводя итоги: в общем, в те времена, когда браузеры разрешали всего два одновременных подключения, domain sharding улучшал ситуацию гораздо сильнее, но и сейчас его использование имеет смысл.
Вкратце опишу ситуацию: имеется довольно длинный одностраничный сайт, в процессе загрузки совершается чуть больше сотни запросов на загрузку статики (css, js, шрифты, изображения). Сайт написан с использованием Ruby on Rails 4.1.12, в качестве веб-сервера — puma-2.15.3, nginx отдаёт статику и смотрит на пуму. Запущено это всё на дроплете Digital Ocean в локации Frankfurt 1. И, имея такие начальные данные, нам надо перенести запросы на статику с доменов вида example.com на домены вида assets%{i}.example.com.
Прежде всего надо настроить отдачу статики с этих адресов. Для этого достаточно настроить соответствующие DNS-записи (у меня было просто установлена запись для *.example.com, в моём случае этого было достаточно), а затем изменить настройки nginx'а (в директиве server_name стоит регулярка, отлавливающая хосты вида assets0.example.com и assets0.example.ru, т.к. в моём случае сайт доступен с двух разных адресов):
server {
listen 80;
server_name ~^assets\d\.(example\.com|example\.ru)$;
root /home/deployer/sites/example/current/public;
location ~ ^/assets/ {
expires 1m;
add_header Cache-Control public,max-age=259200;
break;
}
}
Затем необходимо изменить генерацию путей к статике на стороне приложения. В рельсах это элементарно: достаточно в config/production.rb добавить строчку
config.action_controller.asset_host = "assets%d.example.com"
Тогда рельсы при генерации адресов будут чередовать хосты «assets0.example.com», …, «assets3.example.com». Кстати, я задавался вопросом, почему именно 4 адреса, а не 118 (по одному на каждый запрос, чтоб совсем прям параллельно-параллельно было). Во-первых, для каждого дополнительного хоста будет выполняться DNS lookup, и размещение на странице такого количества хостов только замедлит загрузку. Во-вторых, браузеры кроме лимита на количество одновременных запросов к одному хосту имеют лимит на общее количество одновременных запросов (конкретное значение лимита приведу в конце поста).Магия рельс — это, конечно, хорошо, но в моём случае она не сработала бы из-за необходимости генерировать разные адреса при посещении сайта с разных доменов. Впрочем, настраивается это не сильно сложнее. Также я решил настроить возможность опционального включения/отключения domain sharding на сайте без необходимости изменения кода приложения. Проще всего это было сделать с использованием переменных окружения:
if ENV['DOMAIN_SHARDING'] == 'enabled'
config.action_controller.asset_host = Proc.new { |source, request|
if request
"assets#{source.hash % 4}.#{request.host_with_port}"
end
}
end
Определение порядкового номера домена выглядит именно таким образом (source.hash % 4), чтобы при перезагрузке страницы (ну или при сбросе кэша сервера) для каждого файла ссылка оставалась неизменной. Это позволит эффективнее использовать кэш браузера. Также для меня остаётся загадкой, зачем нужна проверка на существование request, но в доках было написано именно так, и я не стал копаться глубже.
Запускаю
$ DOMAIN_SHARDING=enabled rails s -e production
и всё работает! Ну, почти. Шрифты сломались, и в консоли браузера жалобы на Cross-Origin Resource sharing policy.Текст сообщения
Font from origin 'http://assets1.localhost:3000' has been blocked from loading by Cross-Origin Resource Sharing policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.
С грустной мыслью о том, что не всегда всё работает из коробки, рефлекторно полез узнавать что-нибудь про «Cross-Origin Resource sharing policy rails fonts». Увидел упоминание гема font_assets, в README нашёл строку «Sets Access-Control-Allow-Origin response headers for font assets» и решил, что это как раз то, что мне нужно.
Моя ошибка была в том, что нужно было сначала подумать. Тогда бы я сразу понял, что шрифты, как и остальная статика, на боевом сервере отдаются nginx'ом, который ни о каких гемах знать не знает. На деле же вышел небольшой квест: после подключения font_assets сломалось всё; нашёл, почему сломалось, поправил исходники, заработало; сделал форк, прописал его в Gemfile; обновил версию на продакшене; понял, что надо было сначала подумать; откатил версию, удалил форк.
Собственно, исправление ситуации на продакшене было простым: небольшая правка секции location решает проблему:
location ~ ^/assets/ {
expires 1m;
add_header Cache-Control public,max-age=259200;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods GET;
break;
}
В общем, на этом собственно настройка закончилась и я начал замеры.
Результаты измерений
Замерял так: открыл в инспекторе хрома вкладку Network, в фильтре ставил domain:*.example.com / domain:example.com, и обновлял страницу. Не самый высокотехнологичный способ, но позволяет отслеживать не только время загрузки, но и её характер. (На скриншотах показаны только нижние части графиков).
Со включенным кэшированием, без sharding![]() |
Со включенным кэшированием, с sharding![]() |
Со отключенным кэшированием, без sharding ![]() |
Со отключенным кэшированием, с sharding ![]() |
Со включенным кэшированием, без sharding, Firefox ![]() |
Со включенным кэшированием, с sharding, Firefox ![]() |
Со включенным кэшем итоговое время довольно сильно прыгало вокруг средних значений, но обычно с domain sharding загрузка происходила на ≈0.2-0.4 секунд быстрее. В FF окончание загрузки происходило примерно одинаково, но с включённым шардингом бОльшая часть файлов становилась доступной раньше. Также на графиках наглядно видны ограничения браузеров на количество одновременных соединений: максимум 6 к одному хосту, максимум 10 в целом.
С отключенным кэшем картина сглаживалась, но всё равно c шардингом было немного быстрее. Не совсем понял, почему, но при включении ограничения скорости до 750 kB/s без шардинга работало чуточку быстрее.
Подводя итоги: в общем, в те времена, когда браузеры разрешали всего два одновременных подключения, domain sharding улучшал ситуацию гораздо сильнее, но и сейчас его использование имеет смысл.