Сбор расширенной статистики работы апстрима с помощью nginx-sla

    Введение


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

    Измерить качество обслуживания напрямую мы, конечно, не можем, однако даже такую эфемерную величину в принципе можно свести к набору количественных характеристик, так или иначе косвенно отражающихся на качестве. Прибыль, число клиентов, процент конвертированных лидов (leads – зарегистрировавшиеся или заинтересованные пользователи) и т.д. – все это вполне объективные показатели. Кроме того, эти величины могут быть включены в систему контроля эффективности работы в качестве KPI – ключевых показателей эффективности.

    С нашей, инженерной точки зрения подобными характеристиками являются время ответа и HTTP-код ответа апстрима. Действительно, дизайн, функциональность продукта, маркетинговые усилия и прозвон клиентов находятся вне зоны нашей компетенции. Следовательно, нужно сфокусироваться на том, что находится в нашей власти – ускорение работы инфраструктуры приложения и обработки клиентских запросов.

    Анализ отклика и HTTP-кодов удобно проводить на основе некоторой собранной статистической базы, и здесь мы плавно подходим к теме статьи.

    Что и зачем измеряем


    Как уже было сказано выше, нашей задачей является оценка качества работы приложения, а не фактическое измерение скорости его работы (профайлинг) или оптимизация. Для этого существуют специализированные инструменты. Основными показателями качества работы приложения являются время ответа апстрима и код HTTP-ответа, поскольку именно апстрим выполняет основную функцию генерации контента продукта и обработку пользовательских запросов.

    С другой стороны, анализ скорости передачи сформированной апстримом страницы по каналам на компьютер клиента нас в данном контексте не интересует, т.к. ничего не говорит нам о качестве самого продукта. Клиентская сеть может имеет различную архитектуру и быстродействие, и низкая пропускная способность того или иного клиента может всего лишь означать, что он работает, например, через 3G-модем.

    В этом же смысле малоприменимым на данном этапе является анализ скорости рендеринга страниц в браузере. Доля апстрима в процессе отображения той или иной страницы пользователю с момента получения запроса от него и до окончания загрузки всех компонентов страницы и рендеринга ее браузером составляет около 5-10%. Получается, что, приняв во внимание данные факторы, мы сведем весь результат измерения к значительно и трудно вычисляемой погрешности. Впрочем, это тема совершенно отдельного разговора.

    Таким образом, статистику следует собирать по двум базовым параметрам:
    1. Время отклика апстрима на запрос.
      Анализируя статистику по этому параметру, мы не только сможем оценить общее быстродействие апстрима, но и выявить те или иные паттерны (закономерности) в изменении нагрузки, варьировании среднего времени ответа в различное время суток и т.д.
    2. Код HTTP-ответа
      Понятно, что выявить и устранить все ошибки не удастся – кривые внешние ссылки еще никто не отменял. Однако стремиться к уменьшению числа ошибок, несомненно, стоит. Сохраняться и анализироваться должны как «средняя температура по больнице» (коды 2xx, 4xx), так и точный расклад по всем HTTP-кодам. Преобладание, например, кодов 5xx в статистике укажет на нашу ошибку (а значит, на снижение качества приложения), которую нужно исправить.

    Особенности измерения


    При анализе времени отклика неплохо было бы учитывать характер запроса, обрабатывая и анализируя статистику по различным видам запросов отдельно. Действительно, тяжелый SQL-запрос может значительно увеличить время формирования страницы, тогда как мелкие AJAX запросы, выполняемые пользователями, лишь незначительно «загружают» апстрим. В этой связи наиболее целесообразно было бы выделить категории запросов с различным предельным временем отклика, например:
    • AJAX запрос – 200мс
    • Генерация страницы – 1с
    • Ошибка – 5с
    • Timeout или отсутствие ответа – 7с

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

    В этом случае можно сформулировать общие правила оценки времени отклика на основе процентно-временных диапазонов. Например:
    • 90% < 200мс
    • 95% < 500мс
    • 99% < 1с
    • 100% < 2с

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

    Как оценить результат измерения


    Собрать статистику (о чем ниже) и получить результаты измерений времени отклика – это лишь полдела. Без последующего анализа и оценки результата (хорошо, плохо), все это не имеет никакого смысла. К счастью, здесь нам на помощь приходят промышленные стандарты на время отклика системы:

    В соответствии с этим стандартами, максимальное время ожидания пользователя не должно превышать 10-15 секунд, среднее время обработки запроса должно быть не более 2-5 секунд, а время отклика системы – 0.2-0.5 секунды (стандарты писались из расчета возможностей техники тех времен, которая на сегодняшний день хорошо увеличила свою производительность).

    Другой источник – «Designing and Engineering Time: The Psychology of Time Perception in Software» (2008, www.amazon.com/Designing-Engineering-Time-Psychology-Perception/dp/0321509188) – рассматривает подход, основанный на времени ожидания пользователя:
    • Мгновенную реакцию (0.1 — 0.2 с) система должна показывать при нажатии кнопок, вызове меню, взаимодействии с другими элементами интерфейса.
    • Незамедлительная реакция (0.5 — 1 с) должна следовать в ответ на простой запрос пользователя.
    • Непрерывающаяся реакция (2 — 5 с) – это время, в течение которого пользователь сохраняет внимание на задаче, иначе говоря, время обратной связи.
    • Вынуждающая реакция (7 — 10 с) – когда после 7 секунд пользователь переключается на другую задачу или уходит со страницы.

    Таким образом, сопоставляя статистические данные с указанными величинами, мы можем осуществить преобразование количественной характеристики (время отклика апстрима) в качественную – соответствие реакции системы стандартам и субъективному восприятию пользователя.

    Перейдем к непосредственному измерению.

    Чем измерять


    Рассмотрим решение задачи сбора статистики для популярного сервера nginx.

    При использовании nginx в качестве front end, все необходимая нам информация доступна в логах. Классический способ сбора статистики через анализ логов подразумевает подробный разбор (парсинг) содержимого логов за выделенный промежуток времени. Данный путь имеет ряд недостатков:
    • анализ логов сам по себе требует существенных затрат времени и ресурсов;
    • анализаторы логов часто недешевы, а написание собственного решения требует дополнительного привлечения разработчиков;
    • экспорт результатов парсинга для дальнейшего анализа затруднен.

    Второй подход – использование дополнительных модулей. Рассмотрим ряд вариантов.
    • nginx-statsd
      Модуль для nginx, собирающий статистику и перенаправляющий ее демону StatsD по протоколу UDP. Такой подход, во-первых, требует отдельного сервера, а во-вторых, приводит к росту служебного трафика. Кроме того, StatsD завязан на Graphite, а интеграция с ним других систем мониторинга затруднена.
    • pinba
      Модуль для nginx, собирающий статистические данные в момент завершения выполнения запроса и передающий их по протоколу UDP на отдельный сервер сбора статистики. Фактически, недостатки такого решения обусловлены самим принципом его работы: необходимость отдельного сервера, «лишний» UDP-трафик. Кроме того, модуль фактически фиксирует время от момента получения запроса до его завершения (shutdown), т.е. измеряет время выполнения запроса, а не время ответа апстрима, которое нам нужно.
    • nginx-sla
      Модуль nginx-sla реализует сбор статистики апстрима без привлечения дополнительного сервера. Данные статистики формируются по запросу к nginx в обычном текстовом формате, следовательно, ни отдельный сервер, ни UDP не нужны.

    Следует отметить, что описанные недостатки приведенных выше способов сбора статистики, конечно, никоим образом не ограничивают полезности данных подходов в целом. Однако именно для нашей задачи наиболее простым и гибким инструментом оказывается nginx-sla.

    Преимущества nginx-sla


    Как уже отмечалось, применение модуля nginx-sla не требует выделенного сервера для сбора и анализа статистики. Статистика собирается непосредственным анализом логов nginx и записывается в виде простого текста. Очевидно, что в контексте дальнейшего анализа, как стандартными инструментами, так и сторонними средствами (например, zabbix), такое представление статистики апстрима намного более выгодно.

    Пример статистики, собранной nginx-sla, при конфигурации по умолчанию:
    main.all.http = 1024
    main.all.http_200 = 914
    ...
    main.all.http_xxx = 2048
    main.all.http_2xx = 914
    ...
    main.all.time.avg = 125
    main.all.time.avg.mov = 124
    main.all.300 = 233
    main.all.300.agg = 233
    main.all.500 = 33
    main.all.500.agg = 266
    main.all.2000 = 40
    main.all.2000.agg = 306
    main.all.inf = 0
    main.all.inf.agg = 306
    main.all.25% = 124
    main.all.75% = 126
    main.all.90% = 126
    main.all.99% = 130
    

    Как видно, данные отображают статистику всех обработанных HTTP ответов с различными статусами. Например, значение main.all.http_200 (main.all.http_404, main.all.http_500) обозначает число обработанных ответов с соответствующим статусом.
    А поле main.all.http_2xx содержит количество всех ответов апстрима, имеющих статус «Success».

    Статистика по времени отклика собрана в переменных main.all.300 (main.all.200, main.all.500, main.all.2000). Например, число запросов, обработанных апстримом за время от 300 миллисекунд до 500 миллисекунд, равно 33. А аггрегатированное число запросов, обработанных за 500 мс и менее, равно 266.

    Помимо явной статистики времени ответа апстрима, nginx-sla приводит информацию по перцентилям. В частности время, за которое апстрим отвечает на 90% запросов, составляет в данном примере 126 миллисекунд.

    Перцентильное представление статистики, а также значения среднего (main.all.time.avg) и скользящего среднего (main.all.time.avg.mov) времени ответа позволяют проанализировать поведение апстрима при различных сценариях нагрузки. Например:




    Заключение и выводы


    Теперь настало время вернуться к началу статьи и вспомнить, что нашей целью являлось улучшение качества продукта. Какую информацию мы можем почерпнуть из собранной статистики? Анализируя статистику, полученную за определенный период, закономерно задаться вопросом: лучше стало или хуже? Ведь нашей целью, как мы ранее указали, является преобразование количественных характеристик в качественные.

    Здесь уместно вспомнить два простых закона:
    1. Закон Вебера – Фехнера, утверждающий, что интенсивность ощущения пропорциональна логарифму интенсивности раздражителя. Или иначе: различие в ощущениях появляется, если раздражители отличаются на некую долю своей величины, а не на абсолютное значение.
    2. Абсолютный порог восприятия времени человеком составляет примерно 15 мс.

    Для промежутков времени длительностью до 30 с, порог восприятия разницы составляет примерно 20%. Иными словами, регрессию или ускорение в пределах 20% от актуального значения пользователи не замечают. Разницу свыше 20% пользователь может и обнаружить, но если эта разница окажется меньше минимального порога восприятия времени, то и она пройдет незамеченной.

    Выводы


    1. Качество работы приложения можно свести к набору количественных характеристик. В нашем случае, качество работы апстрима можно свести к времени отклика и HTTP-ответам с различными статусами.
    2. Анализ указанных характеристик удобно проводить модулями для nginx.
    3. В качестве рабочего инструмента используем nginx-sla как наиболее гибкий и нетребовательный модуль в сравнении с аналогами.
    4. nginx-sla позволяет сгруппировать статистическую информацию и, тем самым, преобразовать количественные данные в величины, пригодные для качественного анализа (среднее значение, плавающее среднее, квантили).
    5. Собранная статистика анализируется на предмет превышения предельных значений, заданных стандартом, и сопоставляется с порогом восприятия времени человеком. Делается вывод о необходимости дальнейших действий.

    P.S. Модуль достаточное время без нареканий работает в продакшен среде. Отзывы, баги, pull-request'ы приветствуются.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 10

      +3
      Ох, спасибо за nginx-sla и nginx-statsd!
      По поводу nginx-sla еще надо покопаться, а statsd у нас уже активно используется, и теперь я имею шансы выкинуть костыли в виде скриптов на sh, которые считают статусы ответов по логам nginx, и отдают их в zabbix. Теперь можно просто все отправлять в statsd, а механизм statsd -> zabbix у нас уже работает.
      Ну и красивые графики в graphite, конечно, лишними не будут.
        +1
        Искренне в конце статьи ожидал увидеть примеры настроек и рисовалок…
          +1
          Пример конфигурации на github
          0
          Отличная статья, интересно и приятно было читать. Лично для себя полезными нашел источники по оценке результатов измерений.
            0
            у нас рельные требования для WEB APP выше:
            — до 20 ms в среднем 5 -7 на AJAX
            — до 40 — 50 ms на отдачу сервером страницы
              0
                0
                Добрый день.

                А вы тестировали модуль для случае, когда количество виртуальных хостов > 4?
                У вас в модуле есть баг на эту тему. Когда инициализируется конфигурация main, то создается array на 4 элемента типа pool: ngx_array_init(&config->pools, cf->pool, 4, sizeof(ngx_http_sla_pool_t).
                Когда встречается директива sla_pool, вызывается ngx_http_sla_pool(), вконце которой происходит следующее:
                shm_zone = ngx_shared_memory_add(cf, &pool->name, size, &ngx_http_sla_module);
                shm_zone->data = pool;
                shm_zone->init = ngx_http_sla_init_zone;

                т.е. адрес очередного пула ngx_http_sla_pool_t добавляется в описатель зоны. НО, как только количество виртуальных серверов становится больше чем было инициализировано в ngx_http_sla_create_main_conf, то создается новый array, а старый уничтожается, при этом все ранее созданные описатели зоны хранят указатели на старые пулы…
                и начинает происходить SIGSEGV.
                  0
                  У нас именно виртуальных хостов больше 4 используется. Возможно речь идет не о виртуальных хостах?
                  По этому поводу завели багу. Будет замечательно, если вы приложите ваш конфиг на котором это стабильно воспроизводится в комментариях к issue на github
                  0
                  Да я не корректно поставил вопрос.

                  На самом деле от количества вирт. хостов не зависит, зависит от количества созданных пулов.

                  Вот пример конфига:
                  http {
                  sla_pool one;
                  sla_pool two;
                  sla_pool three;
                  sla_pool four;
                  sla_pool five;
                  sla_pool six;

                  server {
                  listen 192.168.10.10:80 default_server;
                  server_name "";

                  location /one {
                  sla_pass one;
                  }

                  location /two {
                  sla_pass two;
                  }

                  location /three {
                  sla_pass three;
                  }

                  location /four {
                  sla_pass four;
                  }

                  location /five {
                  sla_pass five;
                  }

                  location /six {
                  sla_pass five;
                  }

                  location /sla_status {
                  sla_status;
                  sla_pass off;
                  }

                  }
                  }

                  Вот что выдает gdb (если зайти на 192.168.10.10:80/sla_status):
                  gdb /usr/local/nginx/bin/nginx /usr/local/nginx/logs/coredump/core

                  Program terminated with signal 11, Segmentation fault.
                  #0 ngx_shmtx_lock (mtx=0x28) at src/core/ngx_shmtx.c:78
                  78 if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {
                  (gdb) bt
                  #0 ngx_shmtx_lock (mtx=0x28) at src/core/ngx_shmtx.c:78
                  #1 0x080c7fe7 in ngx_http_sla_status_handler (r=0x9e35908) at /home/fervid/distrib/src/modules/sla/ngx_http_sla.c:793
                  #2 0x0807d2a9 in ngx_http_core_content_phase (r=0x9e35908, ph=0x9e47098) at src/http/ngx_http_core_module.c:1410
                  #3 0x080784c3 in ngx_http_core_run_phases (r=r@entry=0x9e35908) at src/http/ngx_http_core_module.c:888
                  #4 0x080785d4 in ngx_http_handler (r=r@entry=0x9e35908) at src/http/ngx_http_core_module.c:871
                  #5 0x08083780 in ngx_http_process_request (r=r@entry=0x9e35908) at src/http/ngx_http_request.c:1852
                  #6 0x08083dfb in ngx_http_process_request_headers (rev=rev@entry=0xb26ee070) at src/http/ngx_http_request.c:1283
                  #7 0x08084331 in ngx_http_process_request_line (rev=rev@entry=0xb26ee070) at src/http/ngx_http_request.c:964
                  #8 0x08084924 in ngx_http_wait_request_handler (rev=0xb26ee070) at src/http/ngx_http_request.c:486
                  #9 0x0806571e in ngx_event_process_posted (cycle=cycle@entry=0x9e30798, posted=0x81132cc) at src/event/ngx_event_posted.c:40
                  #10 0x0806524d in ngx_process_events_and_timers (cycle=cycle@entry=0x9e30798) at src/event/ngx_event.c:275
                  #11 0x0806c96a in ngx_worker_process_cycle (cycle=cycle@entry=0x9e30798, data=data@entry=0x0) at src/os/unix/ngx_process_cycle.c:816
                  #12 0x0806b013 in ngx_spawn_process (cycle=cycle@entry=0x9e30798, proc=proc@entry=0x806c88f <ngx_worker_process_cycle>, data=data@entry=0x0, name=name@entry=0x80e502d «worker process», respawn=respawn@entry=-3)
                  at src/os/unix/ngx_process.c:198
                  #13 0x0806bd27 in ngx_start_worker_processes (cycle=cycle@entry=0x9e30798, n=4, type=type@entry=-3) at src/os/unix/ngx_process_cycle.c:364
                  #14 0x0806d23b in ngx_master_process_cycle (cycle=cycle@entry=0x9e30798) at src/os/unix/ngx_process_cycle.c:136
                  #15 0x0804e539 in main (argc=1, argv=0xbf8e2f14) at src/core/nginx.c:407
                    0
                    Ошибка исправлена

                  Only users with full accounts can post comments. Log in, please.