Решил я недавно на примере одного проекта узнать, насколько сильно влияет на скорость загрузки сайта 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#{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

Со включенным кэшированием, с sharding


Со отключенным кэшированием, без sharding


Со отключенным кэшированием, с sharding


Со включенным кэшированием, без sharding, Firefox


Со включенным кэшированием, с sharding, Firefox


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

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

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


  1. Tonkonozhenko
    28.12.2015 18:01

    1. Если не ошибаюсь, в Access-Control-Allow-Origin можно указать несколько доменов, а не *.
    2. С .com и .ru версий можно загружать ассеты с одного домена и тогда можно убрать proc из конфигурации.


    1. HedgeSky
      29.12.2015 00:29

      1. Да, можно, и это будет правильно. Хотя я и не могу придумать, как проекту сайту можно навредить, эксплуатируя звёздочку в Access-Control-Allow-Origin. Впрочем, если кто-то не знает об уязвимости, это не значит, что её нет.
      2. В статье я для примера привёл разделение на .com/.ru. На самом деле, в моём случае ресурсы немного разные, хоть и обслуживаются одним и тем же инстансом, поэтому и пришлось разделять ссылки по доменам. А в случае простого синонима — конечно, вы правы, можно обойтись простой строкой в настройках, да.


  1. grossws
    29.12.2015 01:21

    Удивлён, что нет сравнения с банальной загрузкой по http2 (или хотя бы spdy3).


    1. HedgeSky
      29.12.2015 01:35
      +1

      К своему стыду, до вашего комментария я считал http/2 стандартом, поддержка которого ещё не скоро будет реализована. Оказалось, что это уже не так. Постараюсь в ближайшее время его настроить, оценить результаты и дополнить пост. Спасибо за ценное замечание!


      1. grossws
        29.12.2015 01:44

        Настройка довольно проста, если у вас nginx 1.9.5+, то нужно добавить в секцию server

        listen 443 ssl http2;
        ssl_certificate /etc/nginx/ssl/chained.certname.crt;
        ssl_certificate_key /etc/nginx/certname.key;


        Только всё надо будет тестировать по tls, чтобы не получить перекос результатов из-за отсутствия шифрования в тестах из поста.
        Если нет готовых сертификатов, то рекомендую взять cfssl (тулза от cloudflare) и сгенерировать пачку. Ну или, по старинке, openssl.


        1. HedgeSky
          29.12.2015 02:18
          +1

          Спасибо, попробую.


    1. grossws
      29.12.2015 16:00

      Интересно, кто-то не согласен, что без http2/spdy3 обзор выглядит не полным?