Как читать графики нагрузочного теста: восемь диагнозов
Разбор доклада Алексея Лавренюка на Heisenbug 2017. Симптом на графике и что он означает: от одного загруженного ядра до общей очереди запросов.
Самый популярный доклад в этом каталоге записан в 2017 году и набрал 16 тысяч просмотров. Алексей Лавренюк из Яндекса написал специально для доклада сервис на Python Tornado с пятью ручками, каждая со своей поломкой, и последовательно их находит.
Ценность не в инструментах, а в связке «симптом на графике — что он означает». Она не устарела ни на день.
Тайм-коды указывают на запись, любое утверждение проверяется за минуту.
Два графика, на которые смотрят первыми
Слева квантили времён ответа. Чем ниже, тем лучше. Квантиль объясняется без формул: пятидесятый квантиль в 350 мс означает, что в 350 мс уложились половина ответов. Он же медиана. Сотый — максимум, нулевой — минимум [05:00].
Справа распределение времён ответа. Чем выше, тем лучше, потому что высота это число полученных ответов [07:00].
Дальше идут восемь диагнозов, и почти каждый читается по этим двум картинкам.
Диагноз 1. Разрыв между линией и графиком
На графике распределения красная линия показывает, сколько запросов вы отправили, а сам график — сколько ответов получили. Если между ними просвет, сервис перегружен и не успевает отвечать [08:00].
Простейший признак, и его чаще всего пропускают, потому что смотрят только на времена ответа.
Диагноз 2. Полка на ровном числе секунд
Времена ответа выросли и упёрлись в горизонтальную линию на 11 секундах. Это не поведение сервиса, а тайм-аут генератора. Подтверждается графиком сетевых ошибок: там появляются ошибки 110 [19:00].
Полезное следствие: на это состояние вешается автостоп. Тест сам останавливается и записывает достигнутое число как предел производительности [19:00].
Диагноз 3. Процессор упёрся в 3 процента
Машина тридцатидвухъядерная, а загрузка процессора замерла чуть выше трёх процентов. Арифметика простая: 100 делить на 32 — это примерно 3 [21:00].
Значит работает одно ядро из тридцати двух. Проверяется включением помодульного мониторинга: видно, как процесс перескакивает между ядрами, но в каждый момент занято только одно [22:00].
После добавления воркеров процессор загрузился полностью, а число ответов выросло с 20 до 250.
Диагноз 4. Разные времена ответа, одинаковый RPS
Первая ручка отвечает за миллисекунды, вторая за 250 миллисекунд. Логично ожидать, что быстрая отдаёт больше ответов в секунду. На графике обе выдают одинаково [24:00].
Объяснение — лучшая часть доклада, и оно про бар рядом с офисом.
Барменов несколько, но заказы они кладут в общую очередь. Коктейль делается две минуты, виски наливается за десять секунд. В итоге все бармены заняты коктейлями, и человек, заказавший виски, ждёт час.
Это блокировка головы очереди. Лечится разделением: один бармен только на быстрые заказы. В генераторе то же самое — отдельные пулы пользователей и отдельные очереди на каждую ручку [26:00].
После разделения пропускная способность быстрой ручки выросла в разы, а медленная осталась на своих значениях. Что и требовалось: теперь видно каждую по отдельности.
Диагноз 5. Время ответа растёт ровно там, где упёрся процессор
Совпадение по времени и есть диагноз: ручка упирается в вычисления [27:00]. В коде оказался расчёт фракталов.
Тут же наблюдение, которое Лавренюк честно оставляет без объяснения: нагрузка растёт линейно, а потребление процессора — нет. Он говорит, что не выяснял причину, и предлагает залезть в исходники Tornado самостоятельно [28:00].
Это, кстати, потенциальное место для оптимизации: линейный рост CPU вместо нелинейного заметно улучшил бы сервис.
Диагноз 6. Ответы отстают от расписания, а времена не выросли
Выглядит как перегрузка, но это не сервис. Это кончился генератор [31:00].
Проверяется формулой. Среднее время ответа 250 мс, значит один поток даёт 1000/250 = 4 запроса в секунду, а всё вместе — четыре тысячи. Ровно в эту цифру и упёрлись [32:00].
Лавренюк выводит эту формулу прямо в докладе и называет её формулой Литтла. Подробнее про неё — в отдельном разборе.
Диагноз 7. Растёт Connect Time при свободном процессоре
Отдельный график разделяет время установки соединения и время обработки запроса. Если растёт первое, а процессор не загружен, упёрлись в число соединений, а не в вычисления [31:00].
Так вскрылась пятая ручка: пятьдесят инстансов её не пошатнули, потому что она ходит во внешний сервис. Чтобы её продавить, понадобилось три тысячи инстансов и 7500 запросов в секунду [30:00].
Диагноз 8. Медиана растёт плавно, старшие квантили скачут
Признак того, что за одним графиком прячутся ручки с разным поведением [46:00]. Квантильный график показывает распределение внутри каждой секунды: квантили разошлись — большой хвост, сошлись — распределение плотное.
Какую модель когда включать
В докладе это разведено чётко [21:00]:
| Задача | Модель |
|---|---|
| Быстро нащупать предел производительности | закрытая, растущее число пользователей |
| Тест для продакшена с автоматикой | открытая, жёсткое расписание |
| Измерить времена ответа для отчёта | открытая, постоянная нагрузка после прогрева |
Про третий случай сказано отдельно: растущая нагрузка и закрытая модель для этого не годятся, а инстансов надо взять с запасом на случай всплеска [33:00].
Оговорка про точность квантилей
Квантили считаются по посекундным корзинам, поэтому они приблизительные. Точность плавает: на малых временах ответа она доходит до микросекунд, на больших падает до 50 или даже 100 миллисекунд [35:00].
Если нужны точные значения, берётся сырой лог генератора и обрабатывается на Python. Формат простой, набор чисел в человекочитаемом виде.
Что устарело, а что нет
Устарел инструментарий вокруг. Phantom, на котором построены примеры, давно вытеснен Pandora. Overload как сервис хранения отчётов остался в прошлом.
Не устарело ничего из перечисленного выше. Разрыв между отправленными и полученными, полка на тайм-ауте генератора, арифметика «сто делить на число ядер», общая очередь — всё это встречается ровно так же в k6, Gatling и JMeter.
Позиция автора, с которой стоит спорить
На вопрос из зала, зачем нагрузочное тестирование, если есть выкатка на часть продакшена, Лавренюк отвечает так [41:00]:
Прод даёт ответ на вопрос «летит или не летит». Нагрузочное тестирование — это осциллограф для разработчика, который показывает узкие места.
И дальше про метод: систему бьют на компоненты, каждый прогоняют отдельно, и знание собирается по кубикам. Он сам признаёт слабое место: корректного масштабирования результата с компонента на всю систему он ни разу не видел.
Закрывает доклад ссылкой на статью с тезисом, что все нагрузочные инструменты лгут, и советом: чтобы с ними совладать, надо в них разбираться [39:00].
Источники
Доклады по теме
- Как (не) надо проводить нагрузочное тестирование — Григорий Кошелев
- Подводные камни в нагрузке — Владимир Ситников
- Учимся анализировать результаты нагрузочного тестирования — Алексей Лавренюк