Прямой ответ
Locust — нагрузочный инструмент, где сценарий пользователя пишется обычным Python-классом (HttpUser с задачами @task), нагрузка масштабируется воркерами, а результаты — latency-перцентили, RPS, процент ошибок — видны в веб-UI в реальном времени. Рабочая методика важнее инструмента: профиль нагрузки из реальности (какие эндпоинты, с какой долей, с каким think time — не «долбим один URL»), ступенчатый рост (найти точку деградации, а не «проверить, что держит»), процентили вместо среднего (p95/p99 — пользовательский опыт; среднее прячет хвосты), и наблюдение за сервером параллельно (нагрузка без метрик сервера — половина картины: где уперлось — CPU/БД/пул соединений). Для быстрых смоук-проверок в CI — --headless с порогом (--check-fail при p95 выше бюджета).
Сценарий: пользователь, а не долбёжка
База: класс от HttpUser с wait_time = between(1, 3) (паузы между действиями — реальные пользователи не жмут непрерывно) и задачами, отражающими профиль трафика (@task(3) на чтение против @task(1) на запись — веса из аналитики/логов). Состояние сессии — в on_start (логин, токены — через JWT-механику) и атрибутах инстанса; параметризация — данные тестовых пользователей из файла (один пользователь на все виртуальные сессии — артефакт: кеш попаданий и блокировки БД искажают картину). Корректность проверок внутри сценария — response.raise_for_status() или явные assert'ы: «200 ошибок при 100% успешных запросах» означает, что сценарий не проверял содержимое (про функциональные проверки API — в статье про тестирование API).
Запуск и масштабирование нагрузки
Локально: locust -f locustfile.py --host= → веб-UI (число пользователей, ramp-up) → графики. Headless/CI: locust --headless -u 100 -r 10 -t 5m --csv=results (100 пользователей, +10/сек, 5 минут, CSV-отчёт). Предел одного процесса — несколько тысяч RPS (Python и GIL — [про ограничения](./python-mnogoprotsessnost-i-gil.md)); выше — мастер + N воркеров (--processes -1` на многоядерной машине или распределённо по хостам). Важно: машина нагрузки ≠ машина сервера (иначе вы измеряете борьбу locust и приложения за CPU), и сетевая близость к стенду (нагрузка через интернет с ноутбука измеряет интернет).
Метрики: что смотреть и как интерпретировать
Панель Locust даёт RPS, latency (median/p95/p99/max), ошибки — читать их нужно связно. Средний latency 80 мс при p99 = 4 с — «в среднем здорово» означает «хвост пользователей страдает»; деградация под нагрузкой читается по форме: линейный рост p95 с ростом RPS — нормально; колено (hockey stick) — точка насыщения какого-то ресурса; ошибки растут раньше latency — вероятно, таймауты/пулы (не ёмкость CPU). Параллельно — метрики сервера (Prometheus/Grafana: CPU, очередь БД, пул соединений, GC — мониторинг стека): нагрузка отвечает «как деградирует», серверные метрики — «почему». Без второй половины нагрузочный тест — гадание.
Типовые находки и их лечение
Что нагрузочные тесты находят чаще всего: пул соединений БД исчерпан (латентность прыгает ступенью при N>pool_size — увеличить пул/оптимизировать запросы; N+1 под нагрузкой всплывает особенно ярко — разбор); синхронные внешние вызовы в обработчике (один медленный внешний API съедает весь пул воркеров — таймауты/circuit breaker/async); отсутствие пагинации (глубокий OFFSET деградирует квадратично — REST-практика); блокирующие вызовы в async-коде (event loop встаёт — asyncio-ловушки); кеширование отсутствует (горячий справочник читается из БД на каждый запрос — паттерны кеша). Каждая находка → фикс → повторный прогон того же сценария (сравнимость результатов — сценарий версионируется вместе с кодом).
CI-интеграция и культура
Полноценные прогоны — на стейджинге по расписанию/после крупных изменений (не в каждом PR — шумно и долго); в PR-пайплайн — короткий смоук (-t 1m, базовый трафик, пороги p95) как регрессионный детектор производительности (воркфлоу — GitHub Actions-гайд). Культура: SLI/SLO по latency фиксируются до теста («p95 < 300 мс при 500 RPS» — измеримый критерий, а не «чтобы не тормозило»); результаты прогонов сохраняются (сравнение релиз-к-релизу ловит медленную деградацию, которую единичный тест не видит). Честная граница методики: нагрузочный тест на стейджинге ≠ прод (масштаб железа, кеш, данные) — цифры переносятся как порядок и форма кривой, а не как абсолют.
Практический сценарий: поиск точки насыщения каталога
Задача: понять, сколько RPS выдержит эндпоинт каталога до деградации. Пишем сценарий: CatalogUser(HttpUser) с wait_time = between(1, 3), в on_start — логин и сохранение токена, задача — GET списка с реалистичными фильтрами (параметры берутся из файла реальных запросов из логов, а не «первая страница без фильтров» — она вся в кеше и ничего не измеряет). Прогон ступенями в headless-режиме: -u 50 -r 10 -t 3m, затем 100, 200, 400 — по три минуты на ступень, между ступенями пауза (система остывает, пулы сбрасываются). Смотрим не среднее, а p95/p99 и процент ошибок на каждой ступени, параллельно — Grafana: CPU приложения, активных соединений БД, попаданий в кеш. Картина складывается в диагноз: до 200 RPS p95 стабилен на 80 мс; на 400 — p99 прыгает к двум секундам при CPU приложения всего 45% — значит, узкое место не процессор, а пул соединений БД (размер 20, на 400 RPS запросы стоят в очереди) и медленный запрос без индекса, который кеш не покрывает. Фикс: пул до 50, индекс на колонку фильтра, кеш справочника на 60 с. Повторный прогон того же сценария: p95 держится на 400 RPS — точка насыщения сдвинулась, цифры «до/после» уходят в отчёт. Один сценарий, сохранённый в репозитории, становится регрессионным тестом производительности для следующих релизов.
FAQ
Locust или k6/JMeter? Locust — сценарии на Python (один язык с приложением, моки/утилиты переиспользуются), кривая входа для Python-команды нулевая. k6 — выше производительность генератора (Go), JS-сценарии; JMeter — тяжеловесная классика с GUI. Для Python-команд Locust — разумный дефолт; при экстремальных RPS — k6.
Сколько пользователей достаточно? Не «пользователей», а RPS целевого профиля: профиль из логов прода (пик × запас 2–3). Тысяча «виртуальных пользователей» без think time — нереалистичная долбёжка; сто пользователей с паузами — ближе к правде.
Можно ли нагружать прод? Только синтетическим трафиком с явной маркировкой и отделёнными данными, и почти никогда — записывающими сценариями. Стандартная практика — стейджинг, приближённый к проду, плюс канареечные наблюдения на реальном трафике после релиза.
Как тестировать WebSocket/gRPC нагрузкой?
Locust поддерживает кастомные клиенты (WebSocket-события через events.request.fire, gRPC-клиент в задачах сгенерированными заглушками — gRPC-основы); готовые клиенты k6 покрывают часть сценариев из коробки.
Источники: документация Locust (включая отладку и headless-режим) — ссылки в шапке.