Domain sharding: реализация на Ruby on Rails и результаты применения

в 14:40, , рубрики: domain sharding, ruby on rails, браузеры, Веб-разработка, оптимизация сайта, Серверная оптимизация, Сетевые технологии, метки:

Решил я недавно на примере одного проекта узнать, насколько сильно влияет на скорость загрузки сайта 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  ~^assetsd.(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#{rand(4)}.#{request.host_with_port}"
    end
  }
end

Для меня остаётся загадкой, зачем нужна проверка на существование 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
Domain sharding: реализация на Ruby on Rails и результаты применения - 1
Со включенным кэшированием, с sharding
Domain sharding: реализация на Ruby on Rails и результаты применения - 2
Со отключенным кэшированием, без sharding
Domain sharding: реализация на Ruby on Rails и результаты применения - 3
Со отключенным кэшированием, с sharding
Domain sharding: реализация на Ruby on Rails и результаты применения - 4
Со включенным кэшированием, без sharding, Firefox
Domain sharding: реализация на Ruby on Rails и результаты применения - 5
Со включенным кэшированием, с sharding, Firefox
Domain sharding: реализация на Ruby on Rails и результаты применения - 6

Со включенным кэшем итоговое время довольно сильно прыгало вокруг средних значений, но обычно с domain sharding загрузка происходила на ≈0.2-0.4 секунд быстрее. В FF окончание загрузки происходило примерно одинаково, но с включённым шардингом бОльшая часть файлов становилась доступной раньше. Также на графиках наглядно видны ограничения браузеров на количество одновременных соединений: максимум 6 к одному хосту, максимум 10 в целом.
С отключенным кэшем картина сглаживалась, но всё равно c шардингом было немного быстрее. Не совсем понял, почему, но при включении ограничения скорости до 750 kB/s без шардинга работало чуточку быстрее.

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

Автор: HedgeSky

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js