Тест, который падает сам: пороги в k6
Как превратить нагрузочный тест из отчёта, который никто не открывает, в проверку, роняющую пайплайн без участия человека.
Нагрузочный тест, результаты которого кто-то смотрит глазами, — это не проверка, а отчёт. А отчёт можно и не открыть.
Разница видна на пайплайне: юнит-тест либо зелёный, либо красный, и просить кого-то посмотреть на него не нужно. Нагрузочный тест выдаёт графики, а решение «прошло или нет» принимает человек, у которого сегодня релиз и полтора часа до конца дня. Угадайте, что он решит.
Порог как условие выхода
В k6 критерий приёмки живёт в самом сценарии.
export const options = {
thresholds: {
http_req_failed: ['rate<0.01'],
http_req_duration: ['p(95)<200'],
},
};
Здесь написано, что доля неуспешных запросов должна остаться ниже одного процента, а девяносто пятый перцентиль времени ответа — ниже двухсот миллисекунд. Нарушено хотя бы одно условие, и k6 завершается с ненулевым кодом: пайплайн краснеет сам, без человека в цепочке.
Код возврата тут вполне конкретный — в файле errext/exitcodes/codes.go он объявлен так:
// ThresholdsHaveFailed indicates that one or more thresholds have failed.
ThresholdsHaveFailed ExitCode = 99
Знать это число полезнее, чем кажется: рядом живут ScriptException со значением 107 и InvalidConfig со значением 104. Большинству CI хватает деления на ноль и не-ноль, но если вы собираете статистику по сборкам, различие между 99 и 107 отделяет настоящую деградацию производительности от сломанного скрипта — а это две разные проблемы и две разные команды.
Несколько порогов на одну метрику
Одним выражением дело не ограничено.
export const options = {
thresholds: {
http_req_duration: ['p(90) < 400', 'p(95) < 800', 'p(99.9) < 2000'],
},
};
Такая запись описывает форму распределения, а не одну точку. Она допускает, что хвост длиннее середины, и запрещает ему уходить дальше двух секунд. Один порог по p(95) этого не выражает: он одинаково пропускает и ровное распределение, и то, где десятая доля процента запросов висит по минуте.
Не ждать конца теста
Часовой soak-тест, который на пятой минуте показал p99 в десять секунд, оставшиеся пятьдесят пять минут занимает стенд впустую.
thresholds: {
http_req_duration: [
{ threshold: 'p(99) < 10', abortOnFail: true, delayAbortEval: '10s' },
],
}
delayAbortEval тут не украшение: первые секунды теста врут всегда. Пул соединений пустой, JIT не прогрет, кеши холодные — и p99 на второй секунде показывает цифру, не имеющую отношения к установившемуся режиму. Без задержки abortOnFail будет глушить тесты, которые на самом деле в порядке.
У облачного запуска есть отдельная оговорка, и она в документации написана прямым текстом. Пороги там вычисляются раз в шестьдесят секунд, поэтому остановка может опоздать на минуту.
Чего порогом не проверить
Не всякая метрика умеет всё.
| Тип метрики | Что можно писать в пороге |
|---|---|
| Counter | count, rate |
| Gauge | value |
| Rate | rate |
| Trend | avg, min, max, med, p(N), где N от 0 до 100 |
Практическое следствие одно, зато неприятное: перцентиль есть только у Trend. Если своя метрика заведена как Counter, а порог хочется по p(95), менять придётся тип метрики, а не выражение порога.
Где пороги держать
Их место в сценарии, а не в настройках CI.
Аргумент простой: критерий приёмки — такая же часть теста, как и сам сценарий. Вынесенный в пайплайн, он живёт отдельной жизнью — сценарий переехал в другой репозиторий, порог остался, и полгода никто не замечает, что проверяется не то. А лежащий рядом с кодом сценария, он попадает в тот же код-ревью и в ту же историю изменений.
Возражение против этого известно: разным окружениям нужны разные пороги, и на dev-стенде двести миллисекунд недостижимы. Но решается оно переменными окружения внутри сценария, а не переносом всего критерия наружу.
Чего пороги не делают
Они не говорят, почему тест упал.
Порог отвечает на вопрос «прошло или нет» и молчит про причину. Красный пайплайн с кодом 99 означает, что p95 уехал за двести миллисекунд, и ничего больше. Дальше нужны трассировка, профилировщик и человек, который умеет их читать. Автоматизация приёмки не заменяет разбор, она освобождает время на разбор, забирая себе ту часть, где человек и так ошибается чаще машины.
Источники
Доклады по теме
- Как (не) надо проводить нагрузочное тестирование — Григорий Кошелев
- Нагружаем банки — Вячеслав Смирнов, Максим Рогожников