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 мал тормозит отдача: медленный клиент, большой файл, узкий канал оба велики тормозит приложение, и никакой 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 и напрямую к приложению.

Главное

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

Комментарии

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

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

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