Два инструмента чаще всего называют, когда заходит речь о нагрузке: k6 со скриптами на JavaScript и ветеран Apache JMeter с планами из потоков и таймеров. Оба умеют давить на сервис потоком виртуальных пользователей, оба считают латентности и строят отчёты. Разница начинается раньше, чем выбор кнопки: без подготовленного стенда, снятого профиля трафика и включённого мониторинга любой прогон даёт цифры, которым верить нельзя. Статья разбирает механику обоих инструментов, честно сравнивает их и показывает, из чего на деле состоит подготовка, которая превращает нагрузку из лотереи в измерение.
Нагрузка без подготовки и типовые потери на продакшене
Знакомая сценка. Разработчик открывает терминал, запускает на ноутбуке зацикленный вызов API, видит примерно двести ответов в секунду и решает, что сервис тянет двести запросов. Утром этот вывод уезжает в архитектурное обсуждение, а вместе с ним и решение о выборе инфраструктуры. На деле устройство разработчика потянуло лишь собственный сетевой стек, сервер всё это время скучал, а честный потолок оказался в четыре раза выше.
Второй вариант горя встречается ещё чаще: нагрузку направляют прямо на продакшен. На первый взгляд это звучит как экономия на стенде, на практике получается каскад побочных эффектов. Реальные пользователи делят ресурсы с виртуальными, кэши прогреваются чужим трафиком и выдают нереалистично низкие латентности, внешние партнёры видят замедление ответов, а службы защиты воспринимают поток как атаку и начинают резать запросы. Итог: цифры кривые, пользователи недовольны, время потеряно.
Причина обеих историй одна: нагрузочное тестирование это не процесс жатия кнопки, а измерительный эксперимент. У эксперимента должна быть чистая среда, известный профиль входа и приборы на сервисе. Инструмент здесь лишь треть дела.
Сценарий на JavaScript и пороги в k6
k6 представляет собой открытый инструмент от Grafana Labs: движок написан на Go, сценарии описываются на JavaScript, запуск происходит одной командой из консоли. Минимальный сценарий выглядит так:
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 50,
duration: '2m',
thresholds: {
http_req_duration: ['p(95)<300'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const res = http.get('https://stage.example.com/api/orders');
check(res, { 'статус 200': (r) => r.status === 200 });
sleep(1);
}
Пятьдесят виртуальных пользователей две минуты стучатся в тестовый контур, а блок thresholds превращает прогон в измерение с критерием успеха: девяносто пятый процентиль латентности обязан уложиться в триста миллисекунд, доля отказов не превышать один процент. Прогон завершается нечулевым кодом возврата, если порог нарушен, и это делает k6 естественным слоем в конвейере сборки: регресс производительности останавливает релиз так же надёжно, как упавший юнит-тест.
Вторая сильная сторона, сценарии с заданным темпом. Модель vus закрытая: фиксированное число пользователей ждут ответа и только потом идут дальше. Реальный пик выглядит иначе, на сервис в секунду приходит заданное число запросов независимо от задержек. Для этого случая существует executor constant-arrival-rate:
export const options = {
scenarios: {
browse: {
executor: 'constant-arrival-rate',
rate: 200,
timeUnit: '1s',
duration: '10m',
preAllocatedVUs: 100,
},
},
};
Здесь уже измеряется то, что важно для ёмкости: как сервис держит двести запросов в секунду, когда очередь растёт и латентность ползёт вверх. Лёгкость виртуального пользователя в k6, это горутина, а не операционный поток, позволяет набирать тысячи пользователей на одной машине без виртуальной памяти в терабайтах.
Различие между двумя механизмами проверки стоит проговорить прямо. Функция check фиксирует в отчёте факт, например долю корректных ответов, но прогон не валит: удобно собирать статистику по частным инвариантам. Пороги работают жёстче и по итоговым агрегатам: они определяют, считается ли прогон успешным вообще. Вместе они дают и диагностику, и вердикт. Когда стенда в своей инфраструктуре не хватает, существуют облачные исполнения и запуск через Kubernetes-оператор, где одни и те же скрипты исполняет распределённая толпа генераторов.
План из потоков и таймеров в JMeter
Apache JMeter живёт в пятой ветке версий, написан целиком на Java и строится вокруг дерева элементов: группы потоков, семплеры, таймеры, утверждения, слушатели. Виртуальный пользователь здесь поток, которому задаются число, время разгона и число повторений. Простые планы собираются мышью без единой строки кода, и в этом сила инструмента для команд без сильной тяги к программированию.
Ключевая дисциплина, которую проповедует сама документация JMeter: интерфейс подходит для отладки плана, а нагрузка гоняется в консольном режиме. Причина проста: слушатели в интерфейсе съедают память на сбор статистики, и стенд ложится от самого инструмента раньше, чем от нагрузки. Рабочая команда выглядит так:
jmeter -n -t checkout-plan.jmx -l results.jtl -e -o ./report
Флаги просят неинтерактивный прогон, журнал результатов и итоговый HTML-отчёт со сводками и графиками. Тонкую настройку темпа дают таймеры: постоянная пропускная способность и её плавная ступенька из плагинов позволяют плавно поднимать давление и наблюдать, где ломается кривая латентности. Backend Listener отдаёт метрики в InfluxDB или Prometheus, откуда их забирает дашборд Grafana.
Живые сценарии почти всегда требуют связности: войти в систему, вытащить токен из ответа, подставить его в заголовки следующих запросов. Для этого служат экстракторы значений из ответов, регулярные выражения и JSON-выражения, а переменные подставляются в шаблоны последующих семплеров. Разнообразие виртуальных персон приносит настройка чтения из CSV-файла: каждый поток берёт свою строку с учётной записью и параметрами, и толпа перестаёт быть клонированной. Утверждения на ответах довершают картину, отмечая не только факт ответа, но и его осмысленность.
Второй козырь JMeter, широта протоколов. Помимо HTTP он умеет ходить в базы через JDBC, в брокеры сообщений через JMS, в LDAP, почтовые службы, чистые TCP-сокеты. Когда интеграция сложнее одного REST-эндпоинта, это решает. Для больших масштабов существует распределённый запуск: управляющая машина рассылает план рабочим узлам, каждый узел поднимает свою долю пользователей, результаты собираются в общий журнал.
Выбор между инструментами по практическим критериям
Сравнение на практике сводится к нескольким осям. Скрипт k6 лежит в репозитории обычным текстом, проходит ревью в чужих изменениях, параметризуется переменными среды и запускается в CI одной строкой, для современных конвейеров это крупный плюс. План JMeter хранится в XML, который с трудом читается в чужих правках, зато собирается визуально и отдаёт готовый HTML-отчёт без написания кода. Ресурсная сторона также различается: тысячи потоков Java требуют заметной памяти и часто нескольких узлов, тогда как k6 ужимает ту же виртуальную толпу в один процесс.
Протокольный охват остаётся за JMeter, скорость и компактность за k6. Существует и третья ось, людская: если в команде сильны разработчики, сценарий на JavaScript читается как программа с циклами, проверками и собственными функциями; если команда аналитическая, мышиную сборку плана освоить проще, чем язык сценариев. Здоровая практика встречается и такая: JMeter для разнородных интеграционных потоков, k6 для HTTP-контура с порогами в конвейере. Инструмент выбирается под задачу и под людей, а не по хайпу.
Подготовка стенда и профиль реального трафика
Вот здесь начинается настоящая работа, о которой туториалы обычно умалчивают. Достоверное измерение требует, чтобы стенд был похож на продакшен в главном: те же версии ядра, СУБД и промежуточного слоя, сопоставимая конфигурация и объём. Стенд вдвое слабее продакшена даёт не половину ёмкости, а вовсе другую кривую, узкие места размазаны иначе. Полную копию тяжёлой системы мало кто держит, разумный компромисс, копия горячего пути: та же схема, сопоставимые объёмы записей, те же пулы соединений.
Второй ингредиент, профиль трафика. Снимать его нужно с реальных журналов доступа: сколько запросов в секунду в часы пик, как распределяются вызовы между эндпоинтами, какие операции тяжёлые, каков суточный ход. Тогда сценарий нагрузки воспроизводит жизнь, а не воображение автора теста: на оформление заказа приходится десять процентов вызовов, и в сценарии ему достаётся десять процентов давления. Отдельно фиксируются состав фикстур и начальное состояние: прогретый кэш даёт одну кривую, холодный совсем другую, и обе имеют право на существование, если вы знаете, какую именно измеряете.
Третий ингредиент, изоляция. На стенд не должен капать посторонний трафик, ночные задания, синхронизации. Прогон согласуется с командами, которые делят стенд, окно выбирается заранее. Всё это скучная дисциплина, и именно она отделяет измерение от впечатления.
Мониторинг во время прогона и поиск узкого места
Нагрузка без приборов это стрельба вслепую. До первого прогона на стенде должны быть видны метрики всех слоёв: процессор и память узлов приложения, дисковые операции, сеть, пулы соединений к СУБД, число активных транзакций, длина очередей брокера, частота сборки мусора. Инструментальную статистику k6 или JMeter стоит смотреть рядом с серверной: часто они рассказывают согласованную историю, где на определённом уровне давления процессор достигает насыщения, а латентность отрывается от земли.
Поиск узкого места идёт по простой логике противоречия. Латентность растёт, а процессор стоит, ищите ожидание: блокировки, пул соединений, внешний вызов. Процессор упёрся в потолок, ёмкость исчерпана честно, масштабируйте горизонтально или оптимизируйте код. Скачки сборки мусора, ищите лишние аллокации на горячем пути. Диск и журнал транзакций на пределе, вопрос к СУБД и схеме. Каждый прогон сужает гипотезы, и только после исправления стоит повторить измерение, чтобы подтвердить эффект. Нагрузочное тестирование ценен именно этим циклом, а не финальной цифрой в презентации.
Прогон прогону рознь, и полезно различать типы. Базовый уровень подтверждает, что сервис держит ожидаемый профиль без деградации. Стрессовый доводит давление до явного отказа и отвечает, где именно кривая ломается и как система ведёт себя за гранью, плавно деградирует или падает каскадом. Длительный прогон на несколько часов вылавливает медленные беды: утечки памяти, рост очередей, деградацию кэшей и разрастание журналов. У каждого типа своя цель, и подмена одного другим оставляет ровно ту дыру в знаниях, которая потом всплывёт в продакшене.
Порядок первого честного нагрузочного прогона
Чтобы первый прогон не оказался бутафорией, разумно идти по проверенному маршруту:
- Зафиксируйте цель в виде чисел: целевая пропускная способность и допустимые проценти латентности из требований к сервису;
- Снимите профиль трафика с журналов и соберите из него сценарий с реалистичным распределением вызовов;
- Поднимите стенд, близкий к продакшену по конфигурации и объёму, и подготовьте фикстуры с прогретыми и холодными вариантами кэшей;
- Включите мониторинг всех слоёв и убедитесь, что видите метрики серверов не хуже, чем статистику инструмента;
- Начните с малого уровня давления, удостоверьтесь в согласованности приборов и ступенями доведите нагрузку до цели;
- После каждой правки повторяйте прогон и сравнивайте кривые, а не отдельные числа.
Честный вывод такой. k6 и JMeter решают общую задачу, но с разных сторон: первый даёт программные сценарии с порогами для конвейера, второй визуальную сборку и богатый набор протоколов. Выбор инструмента, однако, наименее рискованное решение во всём этом деле. Реальный риск сидит в соблазне пропустить подготовку: нагружать продакшен напролом, без стенда и приборов, весело и быстро. Именно такой путь превращает измерение в случайные цифры, а сервис в источник жалоб. Подготовленный эксперимент стоит дороже по времени и дешевле по последствиям, и лишь он отвечает на вопрос, ради которого всё затевалось: где предел сервиса и что мешает его двигать выше.