1. 1
  2. 2
  3. 3
  4. 4

# теория · шаг 2 из 4

Как измерить эффект и не обмануться

Коротко

Любую настройку из прошлого урока легко включить и невозможно оценить на глаз. Этот урок - про то, как не поверить в улучшение, которого не было.

  • Сначала раздели: $request_time большой при маленьком $upstream_response_time - задержка вне обмена с бэкендом; оба большие - проверь приложение, его зависимости и передачу ответа.
  • Смотри процентили, а не среднее: среднее прячет хвост.
  • Мерить надо ту сторону, где идут данные, и повторять прогоны: на трёх прогонах картина может выйти обратной.
  • Состояние кеша между прогонами обязано быть одинаковым, иначе сравниваешь не то, что думаешь.

Если умеешь читать процентили и не путаешь $request_time с $upstream_response_time - листай до «Теперь сам».

Сначала ответь сам

Включили sendfile, прогнали нагрузочный тест - стало на 15% быстрее. Можно записывать в достижения?

Пока нет. Между двумя прогонами изменилось не только sendfile: сменилось состояние кеша страниц, кеша бэкенда и открытых соединений. Классическая ошибка - перезапустить nginx, прогнать тест и записать разницу на счёт правки.

Правильный вывод из такого замера один: надо прогнать ещё раз. И желательно не два, а больше - об этом ниже, с числами.

На что это похоже

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

Честный замер требует одинаковых условий до и после: те же весы, то же время суток, то же состояние. У сервера «состояние» - это прогретые кеши и установившиеся соединения.

Механизм: три числа отвечают на вопрос раньше догадок

$request_time велик, $upstream_response_time мал тормозит не бэкенд: клиент, большой файл, узкий канал, очередь limit_req оба велики тормозит приложение, и никакой sendfile не поможет $upstream_cache_status в основном MISS до тюнинга отдачи разберись с кешем
Три строки в логе отвечают на вопрос «что чинить» без единого эксперимента.
/etc/nginx/nginx.confhttp
log_format perf '$status $body_bytes_sent $request_time $upstream_response_time '
                '$upstream_cache_status "$request"';

Считать «в среднем» бесполезно: среднее прячет хвост. Смотри 95-й и 99-й процентили - именно они описывают то, что чувствует живой человек.

Правило

$  команда
ab -n 2000 -c 50 https://shop.local/assets/app.4f2c1b.js

ab (ApacheBench) - утилита нагрузки: -n 2000 всего запросов, -c 50 одновременно. Ниже - две строки из её отчёта (полный длиннее: он печатает таблицу 50/66/75/80/90/95/98/99/100%):

↳  выводab -n 2000 -c 50 … (фрагмент отчёта: RPS и два процентиля)
Requests per second:    4821.55 [#/sec] (mean)
  95%     14
  99%     28

Процентиль - это значение, ниже которого укладывается столько-то процентов запросов: «95% 14» значит, что 95 из 100 ответов пришли быстрее 14 мс. Четыре правила, без которых замер врёт:

  1. Один параметр за раз. Включили sendfile - измерили. Потом tcp_nopush - измерили. Иначе непонятно, что дало эффект.
  2. Греть перед замером. Первые запросы читают файл с диска и наполняют кеш; сравнивать надо установившийся режим.
  3. Мерить не с той же машины, если проверяешь сеть: у петли другая физика.
  4. Смотреть на процессор сервера, а не только на цифры клиента. top во время прогона показывает, упёрся ты в nginx или в канал.

Разбор: три прогона могут соврать

Замер из десятого раздела - сравнение HTTP/2 и HTTP/3 на сети с потерями. По трём прогонам, взяв лучшее время, HTTP/3 выглядел победителем: 1513 мс против 4461. Повторили - и HTTP/3 показал 5736 против 5220, то есть проиграл.

Семь прогонов расставили всё по местам:

↳  выводпотери 3%, семь прогонов, отсортированные времена, мс
HTTP/1.1 (шесть соединений)  1017 1120 1214 1237 1341 1420 1767
HTTP/2  (одно соединение)    1313 1413 1714 2021 5221 5268 5486
HTTP/3  (одно соединение)    2744 6250 6464 7532 7807 7934 9306

x6 в первой строке значило «шесть параллельных соединений HTTP/1.1» - развёрнуто ради ясности. Разброс у HTTP/2 - четырёхкратный, у HTTP/3 - трёхкратный. На такой выборке «лучшее из трёх» - это не измерение, а лотерея. Правило: чем больше разброс, тем больше нужно прогонов, и смотреть надо на медиану вместе с хвостом, а не на одно число.

Разбор: мерить надо ту сторону, где данные

Тот же замер сначала не показывал вообще ничего: HTTP/2 давал 813 мс и при нуле потерь, и при пяти процентах. Потери стояли на клиенте - то есть на стороне, где идут короткие запросы, а не мегабайты ответов.

Как только потери переехали на сторону сервера, разница проявилась немедленно: HTTP/2 вырос с 814 до 2021 мс по медиане.

Обобщение простое: инструмент надо ставить туда, где течёт трафик. Меряешь отдачу - ограничивай канал на отдающей стороне. Меряешь загрузку файлов - на клиентской.

Что ломается без этого

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

Стоило вычистить каталог перед прогоном - и картина стала однозначной:

↳  выводдесять одновременных запросов, ответ бэкенда 2 секунды
без proxy_cache_lock:  10 обращений к бэкенду
с proxy_cache_lock:     1 обращение (1 MISS + 9 HIT)

Полезный побочный факт: сам по себе кеш перезапуск переживает - файлы на диске никуда не деваются, и прогретая запись остаётся HIT после restart. Это отличает его от счётчиков limit_req, которые обнуляются.

Зачем это в работе

Порядок приёмов по отдаче, от большого эффекта к малому:

приём что улучшает
кеш ответов бэкенда $upstream_response_time уходит из картины целиком
сжатие размер ответа, а значит время на медленных каналах
кеш-заголовки (пятый раздел) повторные запросы исчезают вовсе
sendfile плюс tcp_nopush процессорное время на раздаче
aio/directio только на файлах больше памяти
worker_processes, worker_connections ничего, пока ты в них не упёрся

Последняя строка - самая частая ошибка. Воркеры и лимиты соединений крутят первым делом, хотя они не ускоряют ни один запрос: они задают потолок, после которого сервер начинает отказывать. Нет в логе worker_connections are not enough - не трогай.

Теперь сам

Пользователи жалуются на медленный сайт. В логе $request_time в 99-м процентиле - 4 секунды, $upstream_response_time - 3,9 секунды. Что чинить и что точно не поможет?

Чинить приложение или базу: почти всё время уходит на бэкенд. Не помогут ни sendfile, ни tcp_nopush, ни воркеры - они про отдачу, а отдача занимает десятую долю секунды. Первый ход - кеш ответов из следующей главы: он убирает $upstream_response_time из картины для той части запросов, которые можно кешировать. Проверить гипотезу можно за тридцать секунд, сравнив TTFB (time to first byte, время до первого байта - curl -w '%{time_starttransfer}') через nginx и напрямую к приложению.

Главное

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

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

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