В стремлении к быстрому сайту многие разработчики упускают один важный момент: клиентскую (фронтендную) точку отказа (frontend SPOF – single point of failure). Причем, почти все инструменты тестирования скорости загрузки сайта также не выявят потенциальных проблем.


Суть клиентской SPOF


Возможность клиентской SPOF определяется особенностями рендеринга страниц браузерами. Для лучшего понимания механизма рендеринга рекомендую почитать мою предыдущую статью о критическом пути рендеринга страниц: habrahabr.ru/post/262239.
Типичный пример клиентской SPOF:
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>jQuery UI Datepicker - Default functionality</title>
  <link rel="stylesheet" href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css">
  <script src="//code.jquery.com/jquery-1.10.2.js"></script>
  <script src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
  <link rel="stylesheet" href="/resources/demos/style.css">
  <script>
  $(function() {
    $( "#datepicker" ).datepicker();
  });
  </script>
</head>
<body>
 
<p>Date: <input type="text" id="datepicker"></p>
 
</body>
</html>

Главное, что мы знаем о критическом пути рендеринга: браузер блокируется на JS-коде, который он встречает на странице и на CSS-файлах (подробности пока опустим). Это значит, что мы не можем увидеть страницу (всю или её часть) до получения критических JS и CSS-ресурсов. Если эти ресурсы находятся на том же сервере, что и сайт, то это дело времени: подождём, загрузим и выполним (или быстро получим 404).
А что, если мы грузим ресурсы с других серверов? В этом случае браузер сначала должен сделать DNS-запрос, подключиться к серверу, послать запрос и подождать ответ. И только потом обработать полученный ресурс и продолжить рендеринг страницы.
Вот здесь мы и подошли к пониманию клиентской SPOF. Если на вашем сайте есть критический JS или CSS-ресурс, загружаемый из внешнего источника (другой домен), вы получаете клиентскую точку отказа (SPOF). При этом внешним источником может выступать любой сервер: узел CDN, внешний сайт с JS-API, хостинг JS-библиотек и т. д. При медленном ответе внешнего сервера получаем гарантированное проявление отказа (медленный или очень медленный рендеринг страницы). Если внешний сервер вообще не отвечает на запросы, браузер будет ждать до окончания таймаута (может быть до десятков секунд).
Это может выглядеть так (обратите внимание на время над скриншотами).
image
Для пользователя проявление SPOF в худшем случает выглядит как «сайт не работает», в лучшем случае как «сайт очень медленный». При этом ваш хостинг работает отлично, атаки на сайт нет, а эффект как от хорошего DDoS'а.

Почему это важно?


Основополагающая статья о проблеме была написана Стивом 1 июня 2010 года, однако постоянно приходится видеть сайты, которые подключают JS-библиотеки c внешних хостингов (apis.google.com, code.jquery.com, userapi.com, facebook, twitter и т. д.) Опасность составляет подключение скриптов в верхней части HTML-кода в синхронном режиме (без атрибута «async»).
Хорошо, всё это страшно, но мы же грузим файлы с супер-надёжных, никогда не падающих распределённых CDN-хостингов! Они всегда отвечают и делают это быстро! Проблемы вроде и нет…
К сожалению есть, вот почему.
  • Даже самые надёжные хостинги падают (даже облачные).
  • Точки раздачи CDN могут быть далеко от пользователя (многие не имеют точек в России).
  • Сервисы раздачи статики и хостинги библиотек часто бесплатные и не имеют никаких гарантий работоспособности и скорости отклика.
  • Хост-источник может быть заблокирован на уровне провайдера/страны (например, в Китае или у нас в России).

В результате мы получаем бомбу замедленного действия. Обычно всё отлично работает, но наступает момент, когда сайт вдруг «падает». Особенно весело, что проблема может быть плавающей или затрагивать только часть пользователей.

Проверка на наличие и тяжесть последствий от SPOF


Что со всем этим делать? Во-первых, нужно выяснить наличие SPOF на сайте. Для этого собираем все внешние хосты, с которых идет загрузка JS и CSS-ресурсов (также можно выяснить источники подключаемых шрифтов).
Во-вторых, нужно проверить, что будет при сбое внешних хостов. Лучшим средством для этого я считаю Webpagetest.org с закладкой SPOF. Вы можете вписать в это поле все хосты, которые будут тихо дропать пакеты и ничего не отвечать. Выбираем интересующую точку тестирования, браузер и запускаем обычный тест.
image
На выходе получаем два теста: нормальный и SPOF. На просмотре скриншотов рендеринга страницы видим тяжесть последствий SPOF (как правило задерживается момент начала рендеринга). На основе этих данных уже можно принимать решение о переносе ресурсов или страховке от сбоев.

Решение проблемы


Для исключения или минимизации последствий от клиентской SPOF можно предпринять такие меры.
  • Перевести по возможности все JS-библиотеки в асинхронный режим (аттрибут async).
  • Убрать все внешние синхронные JS-библиотеки вниз кода, до закрывающего body.
  • Использовать асинхронные версии подключения внешних API.
  • Стандартные JS-библиотеки перенести на хостинг сайта и грузить с него.
  • Перенести шрифты и их CSS-код на хостинг сайта.
  • Регулярно проверять сайт на наличие SPOF.

Вот и все рекомендации. Если у вас накопился собственный опыт борьбы с клиентскими SPOF, пишите в комментариях.

Что почитать ещё:


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


  1. Nadoedalo
    28.08.2015 17:49

    requireJS && timeout/error callback к собственному серверу?


    1. Nickmob
      28.08.2015 17:59

      Может пойти прямым путём и сразу на собственный сервер?


      1. yoyurec
        28.08.2015 18:08

        да ну, а как же CDN?


        1. Nickmob
          28.08.2015 18:13
          +1

          Здесь нужно смотреть, какая CDN и какой выигрыш от использования. Если это коммерческий сервис, в котором вы уверены, можно грузить прямо с него и не думать — риск будет небольшим. А вот если нет — лучше будет со своего сервера. Недавний пример из практики: CDN для ускорения Битрикса (встроенная фича, на основе CDNVideo) положила сайт (пришлось отключить).


      1. Nadoedalo
        28.08.2015 18:18

        Ну, на маленьких проектах может быть дорого отдавать либы со своего хостинга — ограничения по траффику например. А так и экономия, и fail-safe есть. А так на средних и крупных коммерческих сайтах не вижу смысла загружать из CDN — много рисков, мало профита.


        1. Nickmob
          28.08.2015 18:22
          +1

          По-моему все ровно наоборот. Первый раз слышу о дороговизне отдачи либ с хостинга… Трафика они требуют совсем чуть с учетом сжатия и правильного кеширования.
          Вообще зачем нужен CDN: для географической близости к пользователю и разгрузки каналов сервера, то есть как раз для средних и крупных коммерческих сайтов.


          1. Nadoedalo
            28.08.2015 19:43

            Ну во первых никто не мешает на либы включить «вечный» кеш и они тогда будут отдаваться раз в пятилетку.
            Во вторых — своему CDN не доверять это странно.
            В третьих — примеры предоставлены публичных CDN, потому речь о них была.

            Глупо как-то для разгрузки CDN писать fail-safe с отдачей напрямую с сервака — если CDN упал от нагрузки то за ним упадёт и сервер в таком случае.

            А так вообще «свой» CDN фронту не видно — есть балансер(ы), они сами и решают куда-как быстрее, для фронта это всё та же ссылка на свой сервер а дальше — чёрный ящик.


  1. yoyurec
    28.08.2015 18:00

    Лучшим средством для этого я считаю Webpagetest.org

    или плагин к хрому — SPOF-O-Matic


    1. Nickmob
      28.08.2015 18:04

      Классная вещь, буду использовать!


  1. sunnybear
    29.08.2015 01:25

    Мы в Айри много чего тестировали, но в автоматическом режиме работает только проксирование всего сайта через CDN + перезапись внешних ресурсов на проксирование через сайт. В итоге получается и сайт на CDN, и ресурсы с CDN, и нет DNS-запросов / внешних коннектов на сторонних виджетах.

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


    1. Nickmob
      02.09.2015 10:30

      Интересно, как вы обходите случаи, где ресурс формируется с учетом браузера пользователя (например: Google Fonts). Также нужно как-то отслеживать, нет ли обновлений ресурса на сервере-источнике (например, какой-нибудь JS-API социальных сетей).


  1. Phizio
    29.08.2015 09:36

    Недавно как раз был случай, на одной из страниц проекта — большая datatable со списком юзеров, один из столбцов — аватар (в базе хранились лишь ссылки на вк, fb, Instagram — картинку). И если помните, вк лёг на полдня… Казалось бы, ну не загрузится часть аватарок и фиг с ним? Неет, на странице стоял коварный preloader со спиннером-крутилкой, и он благополучно ждал завершения загрузки всей страницы (в том числе картинок). Как итог мы получили полностью нерабочую страницу. Сразу даже не поняли, т.к. в этот момент ещё апдейты накатывали — соответственно, грешили на них.
    Резюме: прелоадеры следует использовать осторожно, и смотреть их настройки более детально.
    Кстати, решая данную проблему, обнаружил у картинок стандартный тег, который может загружать альтернативную картинку, если по основному href-у недоступна.