Нагрузочное тестирование в k6: как читать результаты
k6 показывает не только среднее время ответа. Разбираем, как выбрать профиль нагрузки, настроить пороги, отличить деградацию сервиса от проблем самого теста и понять, что означают p95, ошибки и dropped iterations.

Сервис может показывать среднее время ответа 180 мс и при этом быть непригодным для запуска: p95 достигает 1,8 секунды, а каждый сотый запрос завершается ошибкой. В k6 важно не просто запустить сценарий, а связать профиль нагрузки, пороги и метрики с конкретным решением: выпускать релиз, искать узкое место или повторять тест.
Почему среднее время ответа вводит в заблуждение
В итоговом отчёте k6 для HTTP-запросов вы увидите минимум, максимум, среднее и перцентили. Среднее значение складывается из всех запросов, поэтому быстрые ответы могут скрыть редкие, но очень медленные.
- p(90) — время, быстрее которого завершились 90% запросов;
- p(95) — время, быстрее которого завершились 95% запросов;
- p(99) — хвост распределения, где видны самые медленные 1% запросов;
- http_req_failed — доля неуспешных HTTP-запросов;
- checks — доля пройденных проверок в сценарии;
- dropped_iterations — итерации, которые k6 не смог запустить вовремя или из-за нехватки доступных ресурсов.
Если 95% запросов укладываются в 500 мс, это не означает, что пользователь всегда получает ответ за полсекунды. Последние 5% могут ждать несколько секунд. Для интерфейсов с большим потоком запросов именно этот хвост часто превращается в жалобы и тайм-ауты.
Перцентиль — статистическая граница, показывающая, какое время не превышает заданная доля измерений. p95 не означает «среднее время для 95% пользователей» и не показывает максимальную задержку.
Результат нагрузочного теста — это не число p95 само по себе, а ответ на вопрос: при какой нагрузке сервис сохраняет заранее определённое качество.
Три профиля нагрузки в k6
Для примеров ниже используется синтаксис k6 0.49.0. Базовые конструкции сценариев и порогов в этой версии совместимы с актуальной веткой, но перед запуском в CI проверьте документацию установленного бинарника.
| Профиль | Механизм в k6 | Что показывает | Цена и риск |
|---|---|---|---|
| Постепенный рост пользователей | ramping-vus | Деградацию при увеличении числа одновременно работающих сценариев | Прост в настройке, но число запросов зависит от времени ответа и пауз в сценарии |
| Фиксированная скорость запросов | constant-arrival-rate | Способность системы обслуживать заданный поток запросов | Ближе к бизнес-нагрузке, но требует корректных значений preAllocatedVUs и maxVUs |
| Короткий резкий всплеск | ramping-arrival-rate | Поведение при скачке трафика и быстром возврате к штатному уровню | Помогает найти проблемы автоскейлинга, но сильнее нагружает тестовый контур |
Когда выбирать каждый вариант
Берите ramping-vus, если хотите воспроизвести пользовательские сессии: открыл страницу, сделал несколько действий, подождал. Используйте arrival-rate-сценарии, если у вас есть целевой поток — например, 50 запросов в секунду независимо от того, сколько времени занимает обработка.
Не сравнивайте результаты этих профилей напрямую. 100 VUs — это не то же самое, что 100 запросов в секунду: один виртуальный пользователь может выполнять один запрос за 100 мс, а другой — за 2 секунды и с паузами между действиями.
Минимальный сценарий с полезными порогами
Ниже — полный файл для k6 0.49.0. По умолчанию он обращается к локальному сервису на http://localhost:8080/. Адрес тестового стенда передаётся через переменную окружения, поэтому секреты и настройки не попадают в код.
import http from 'k6/http';
import { check, sleep } from 'k6';
const targetUrl = __ENV.TARGET_URL || 'http://localhost:8080/';
export const options = {
scenarios: {
browse: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '1m', target: 10 },
{ duration: '3m', target: 10 },
{ duration: '1m', target: 30 },
{ duration: '3m', target: 30 },
{ duration: '1m', target: 0 }
],
gracefulRampDown: '30s'
}
},
thresholds: {
http_req_failed: ['rate<0.01'],
http_req_duration: ['p(95)<500', 'p(99)<1000'],
checks: ['rate>0.99']
}
};
export default function () {
const response = http.get(targetUrl, {
tags: { name: 'home' }
});
check(response, {
'status is 200': (result) => result.status === 200,
'body is not empty': (result) => result.body && result.body.length > 0
});
sleep(1);
}
В HTML-версии символы операторов экранированы: при сохранении файла проверьте, что в JavaScript используются обычные <, > и =>, а не HTML-сущности. Запуск выглядит так:
TARGET_URL=https://staging.example.test/ k6 run script.js
Порог — это условие провала теста, а не рекомендация. Если p95 превысил 500 мс или доля ошибок стала выше 1%, команда получает ненулевой статус завершения и может остановить CI-пайплайн. Значения 500 мс и 1% здесь пример: их нужно заменить на согласованный SLO конкретного сервиса.
Как читать итоговый отчёт
Сначала проверьте, что тест измерял то, что вы хотели
Смотрите на длительность стабильной фазы, количество запросов и реальное число виртуальных пользователей. Разминка и остановка сценария дают переходные значения, которые не стоит смешивать с рабочим плато.
- http_reqs показывает общий объём HTTP-запросов и скорость их выполнения;
- iterations показывает число завершённых проходов функции пользователя;
- vus помогает понять, сколько виртуальных пользователей реально работало;
- http_req_duration включает путь запроса от отправки до получения ответа, поэтому его нужно читать вместе с ошибками;
- http_req_waiting помогает оценить время ожидания ответа от сервера, но не заменяет трассировку и серверные метрики.
Если p95 растёт вместе с VUs, а доля ошибок ещё мала, система сначала упирается в задержку. Если одновременно растут ошибки, очередь запросов и количество тайм-аутов, ищите ограничение в пуле соединений, базе данных, прокси или лимитах самого приложения.
Не путайте check с успешным HTTP-ответом
HTTP-ответ со статусом 200 не доказывает, что операция прошла успешно. Приложение может вернуть страницу ошибки с кодом 200 или JSON с полем error. Поэтому проверки должны валидировать не только статус, но и обязательное содержимое ответа. При этом checks и http_req_failed — разные метрики: первая зависит от ваших условий, вторая отражает ошибки HTTP-запросов, которые k6 классифицирует как неуспешные.
Что обычно ломает интерпретацию
Тестовый стенд не похож на рабочий
Результаты нельзя переносить на продакшен, если в тесте другой размер базы, кеш пустой, отключена авторизация, меньше воркеров или отсутствуют фоновые задачи. Зафиксируйте хотя бы версию приложения, число экземпляров, тип базы, объём данных и параметры генератора нагрузки.
Генератор сам стал узким местом
k6 запускается на машине, которая тоже имеет предел по CPU, памяти, сетевым соединениям и пропускной способности. Если нагрузка упирается в генератор, сервис может выглядеть быстрее или стабильнее, чем есть на самом деле. Смотрите загрузку машины с k6 и при необходимости распределяйте тест по нескольким генераторам, не меняя сценарий без фиксации условий.
В отчёте смешаны разные операции
Главная страница, поиск и оформление заказа имеют разные SLA. Используйте теги name или отдельные сценарии, чтобы не получать один усреднённый показатель для несопоставимых запросов. Иначе быстрый healthcheck может скрыть медленный запрос к каталогу.
Порог выбран после теста
Если сначала посмотреть на график, а потом подобрать удобный предел, тест превращается в подтверждение уже принятого решения. Сформулируйте условия до запуска: допустимый p95, предельную долю ошибок, минимальный поток и длительность стабильной фазы.
Как измерять результат без самообмана
Один запуск не доказывает устойчивость системы. Повторите тест при одинаковых условиях и сравните не только среднее и p95, но и p99, ошибки, throughput, CPU, память, задержки базы и длину очередей. Фиксируйте коммит приложения и конфигурацию инфраструктуры, иначе разницу между двумя запусками будет невозможно объяснить.
Числа «до» и «после» нельзя придумывать заранее. Для каждого изменения сохраните исходный отчёт, затем повторите тот же профиль на том же объёме данных. Если меняется размер стенда или генератор нагрузки, это отдельный эксперимент, а не чистое сравнение оптимизаций.
Что делать дальше
Начните с одного критичного пользовательского пути и одного тестового стенда. Определите SLO, добавьте пороги в сценарий, запустите стабильную фазу и одновременно соберите метрики приложения, базы и инфраструктуры. После этого вынесите запуск в CI только для короткого контрольного профиля, а длительные тесты выполняйте отдельно, чтобы они не перегружали общий контур разработки.
Следующим шагом станет разбор конкретного узкого места: например, корреляция p95 k6 с временем SQL-запросов или длиной очереди. Инвалидация кеша, распределённый запуск и моделирование сложных пользовательских потоков — отдельные задачи, их не стоит прятать в один универсальный сценарий.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: k6 documentation: Built-in metrics, k6 documentation: Thresholds
Частые вопросы
Какое значение p95 считать нормальным?
Почему среднее время ответа хорошее, а p99 плохой?
Чем VUs отличаются от запросов в секунду?
Можно ли запускать k6 по продакшену?
Что делать, если появились dropped_iterations?
Можно ли сравнивать два запуска k6, если менялся стенд?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.