惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Attack and Defense Labs
Attack and Defense Labs
宝玉的分享
宝玉的分享
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
D
Darknet – Hacking Tools, Hacker News & Cyber Security
V
Vulnerabilities – Threatpost
博客园_首页
Engineering at Meta
Engineering at Meta
F
Fortinet All Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
罗磊的独立博客
V
Visual Studio Blog
Know Your Adversary
Know Your Adversary
Hacker News - Newest:
Hacker News - Newest: "LLM"
美团技术团队
L
LINUX DO - 最新话题
The Last Watchdog
The Last Watchdog
博客园 - 三生石上(FineUI控件)
T
Tor Project blog
云风的 BLOG
云风的 BLOG
N
Netflix TechBlog - Medium
MyScale Blog
MyScale Blog
The GitHub Blog
The GitHub Blog
有赞技术团队
有赞技术团队
I
InfoQ
Last Week in AI
Last Week in AI
V2EX - 技术
V2EX - 技术
量子位
S
Secure Thoughts
L
LangChain Blog
The Hacker News
The Hacker News
H
Help Net Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
小众软件
小众软件
K
Kaspersky official blog
Security Archives - TechRepublic
Security Archives - TechRepublic
Google Online Security Blog
Google Online Security Blog
I
Intezer
Vercel News
Vercel News
Hacker News: Ask HN
Hacker News: Ask HN
Cisco Talos Blog
Cisco Talos Blog
Google DeepMind News
Google DeepMind News
S
Securelist
阮一峰的网络日志
阮一峰的网络日志
G
Google Developers Blog
Help Net Security
Help Net Security
Martin Fowler
Martin Fowler
爱范儿
爱范儿
Y
Y Combinator Blog
C
Check Point Blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Без рук: автоматизируем нагрузочное тестирование изменений в CI
daria021 · 2026-05-11 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение7 мин

Охват и читатели568

Кейс

Нагрузочное тестирование — одна из самых избегаемых тем, когда речь заходит о контроле качества ПО. Корпорации, конечно, не обходят его стороной, но если говорить о продуктах меньшего масштаба, то нагрузочное тестирование часто пропускается. Команда (и, в целом, справедливо) полагает, что продукт справится с нагрузкой — на малых объёмах это обычно прокатывает. А потом внезапно наступает день, когда пользователей стало больше, а система не готова.

Почему команды не тащат нагрузку в релизный цикл? Потому что это чаще всего просто не окупается: нужно выбрать движок, описать сценарий, гонять тесты вручную или тратить время на создание собственной обвязки для встраивания в CI, придумать критерии качества и анализировать результаты. Всё это занимает значительное время, а на короткой дистанции часто оказывается оверинжинирингом. Но если формирование требований упростить концептуально невозможно, то всё остальное вполне можно собрать в переиспользуемый инструмент, позволяющий командам легко интегрировать нагрузочное тестирование и регрессионный анализ в свой процесс доставки.

В CI/CD мы хотели простую штуку: на каждый PR запускать короткий перф‑смоук и получать ответ уровня «PASS / WARNING / DEGRADATION», а не 15 минут медитировать над CSV и тратить ценное время на анализ, который, вероятно, не пригодится в ближайшей перспективе. Посмотрим, к чему мы в итоге пришли.

Пример HTML‑отчёта (KPI‑карточки + графики + сравнение с baseline)
Пример HTML‑отчёта: светлая тема

Пример HTML‑отчёта: светлая тема

Тёмная тема
Кастомная тёмная тема
Брендированный отчёт
Preset: errors
Preset: throughput
Preset: latency

Зачем вообще тащить нагрузку в CI

Идея, в общем-то, лежит на поверхности: ловить падение производительности не на проде и не в ночь перед релизом, а ровно в момент, когда оно появились — в PR. Та же самая логика «раннего обнаружения», что и в юнит‑тестах: быстрый фидбек, меньше рисков, меньше ночных алертов. В индустрии это обычно называют shift-left performance testing — нагрузка становится частью пайплайна, а не отдельным ритуалом, который проводят по большим праздникам. [1]

Цель была приземлённая: сделать так, чтобы нагрузочный тест в CI по UX напоминал обычный юнит‑тест. Запустил — получил результат — пошёл дальше.

Если подробнее, нам нужно было получить следующее CI‑поведение:

  • предсказуемо запускать нагрузку (smoke на PR, длиннее на main/ночью);

  • сравнивать с baseline и сигналить о регрессиях;

  • проверять строгие пороги и валить сборку;

  • генерировать отчёт, который не стыдно кинуть в PR;

  • хранить результаты как артефакты, чтобы открыть любой прогон из истории.

Locust отлично решает свою задачу — генерировать нагрузку. Headless‑режим для CI, экспорт статистики и отчётов (--csv, --html, --json) — всё это есть из коробки. [6]

Но дальше начинается взрослая жизнь. CI нужны не числа, а решение — падать сборке или нет. Нужны пороги: p95 < X, error_rate < Y — и это должно быть машинно проверяемо. Нужен baseline и сравнение «как было / как стало», потому что абсолютные цифры без контекста обманчивы. И нужен нормальный отчёт, который можно открыть в артефактах и за тридцать секунд понять, что именно поехало.

Можно, конечно, собрать это самому — парочка скриптов, jq, питон, слёзы… Мы решили пойти другим путём и вынести всё это в отдельный слой поверх Locust.

В итоге получился Locomotive — Python‑библиотека с CLI, которая запускает нагрузку через Locust, а сверху добавляет всё то, чего обычно не хватает для CI: декларативные сценарии, baseline‑анализ, пороги и отчёты.

Что есть из коробки:

  • Декларативная конфигурация в JSON/YAML — можно описать сценарий без locustfile.py (хотя он тоже поддерживается, если нужна сложная логика).

  • Генерация конфига из OpenAPI через loco init --openapi … — удобно, чтобы стартануть быстро и не набивать руками десяток эндпоинтов.

  • Gate checks — абсолютные пороги по метрикам (скажем, p95_ms < 500, error_rate < 1). [9]

  • Regression rules — сравнение текущего прогона с baseline по настраиваемым правилам (относительные/абсолютные отклонения, направление, уровень реакции warn/fail). [10]

  • HTML‑отчёт с графиками и дельтами + настраиваемые темы.

  • Пресеты отчёта (например, errors и throughput) — если нужно быстро сфокусироваться на конкретном аспекте.

  • Готовый GitHub Action, который сам ставит пакет, подтягивает baseline, запускает тест, складывает артефакты и комментирует PR.

Дисклеймер: мы не хотим сказать, что другие инструменты плохие. Более того, при разработке собственного решения мы опирались на фичи существующих решений. У Grafana k6 есть thresholds, которые фейлят тест при нарушении условий — штука, задизайненная прямо под CI. [14]
У Taurus есть pass/fail‑критерии. [16]
JMeter умеет CLI/non‑GUI режим (GUI — только для сборки/отладки плана). [18]

Мы просто хотели сохранить Locust‑экосистему и при этом получить CI‑first поведение без самописного зоопарка.

Как устроено внутри

Теперь — к самому вкусному: что происходит между «PR opened» и «сборка упала, потому что p95 поехал».

Компонентная схема архитектуры (CI/CD Pipeline → CLI → Launcher/Analyzer/Reporter/Storage)
Компонентная схема архитектуры

Компонентная схема архитектуры

Внутри Locomotive логика разбита на несколько компонентов:

CLI — единая точка входа. Через неё происходит всё: «запусти», «сравни», «сгенерь отчёт».

Launcher — запуск Locust с нужными параметрами в headless‑режиме и сбор сырых результатов. По сути, это обёртка над тем, что Locust и так умеет делать — генерировать CSV, JSON‑статы и прочее.

Storage — складывает результаты в артефакты, чтобы CI мог их сохранить и показать.

Analyzer — берёт текущий прогон, сравнивает с baseline и применяет правила. Именно он решает: PASS, WARNING или DEGRADATION. [10]

Reporter — собирает из всего этого HTML‑отчёт, который можно открыть одним кликом в артефактах.

Sequence‑диаграмма (Developer → GitHub Actions → CLI → Locust → Analyzer → Reporter → Storage)
Sequence‑диаграмма

Sequence‑диаграмма

Как считается регрессия

Проверки бывают двух типов, и их полезно разделять.

Первый — gate checks, или абсолютные пороги. Это, по сути, SLA‑ворота: p95_ms не должен быть выше 500 мс, error_rate не должен превышать 2%. Задаёте в конфиге — и всё, пайплайн будет их проверять (пример конфига будет ниже).

Второй — regression rules, или сравнение с baseline. Тут идея другая: не «метрика выше порога», а «метрика стала хуже, чем была». Например, правило из rules.example.json может звучать так: p95_ms не должен вырасти больше, чем на 20% (fail), а рост на 10% — уже warning.

Правила формулируются максимально понятно: mode: relative — сравниваем в процентах; direction: increase — плохо, когда метрика растёт (для latency и error_rate); direction: decrease — плохо, когда падает (для RPS); warn / fail — уровни реакции, от деликатного предупреждения до «режь билд». [10]

Дальше всё просто: если зафиксирована серьёзная деградация (DEGRADATION), CI падает прямо в PR.

Минимальный запуск в CI

Пререквизиты

Установка и генерация конфига

pip install locomotive
loco init

Locomotive ставится из PyPI и требует Python 3.9+. Locust подтягивается как зависимость — отдельно ставить не нужно.

Если у вас есть OpenAPI‑спека, можно сразу сгенерировать конфиг из неё. А если хотите GitHub Actions — и заготовку workflow:

loco init --openapi openapi.json
loco init --github-workflow

Конфиг: гейты и baseline‑правила

В конфиге обычно живут две вещи: абсолютные пороги (gate) и правила сравнения (rules). Выглядит это примерно так: [26]

{
  "load": {
    "host": "https://staging.example.com",
    "users": 20,
    "spawn_rate": 5,
    "run_time": "1m"
  },
  "analysis": {
    "gate": {
      "min_requests": 200,
      "thresholds": {
        "p95_ms": { "fail": 500 },
        "error_rate": { "fail": 2 }
      }
    },
    "rules": [
      { "metric": "p95_ms", "mode": "relative", "direction": "increase", "warn": 10, "fail": 25 },
      { "metric": "rps",    "mode": "relative", "direction": "decrease", "warn": 10, "fail": 20 }
    ],
    "fail_on": "DEGRADATION"
  },
  "report": { "output": "artifacts/report.html" }
}

Полный пример конфига «без locustfile» — со scenario.requests, headers, tags и прочим — лежит в репозитории.

Запуск

loco --config loconfig.json ci

Команда ci — это «полный цикл»: тест → анализ → отчёт. Всё за один вызов.

Если хотите сохранить текущий прогон как baseline (опорную точку для будущих сравнений), добавляете --set-baseline:

loco --config loconfig.json ci --set-baseline

Что вы получаете на выходе

Во‑первых — человекочитаемый ответ: PASS, WARNING или DEGRADATION. Не стена цифр, а один понятный статус. Его же Action возвращает как output, так что на него можно завязывать дальнейшую логику пайплайна.

Комментарий в PR от Action
Компонентная схема архитектуры

Компонентная схема архитектуры

Во‑вторых — артефакты, по которым можно поднять «историю болезни» без археологии. Структура выглядит так:

artifacts/
├── baseline.json
├── history.json
└── runs/
    └── <run_id>/
        ├── run.json
        ├── metrics.json
        ├── analysis.json
        ├── report.html
        └── generated/

Если включить историю (artifacts.history > 0), появляется возможность строить тренды между прогонами. Например, увидеть, что p95 тихонько ползёт вверх последние 20 запусков — до того, как он прорвёт порог и всё загорится красным.

Графики трендов (p95/rps/error_rate по run_id)
Кусок отчёта с трендами

Кусок отчёта с трендами

Перед тем как тащить нагрузку в пайплайн, полезно честно ответить на два вопроса: где она даст быстрый выхлоп, а где превратится в шум. Ниже — короткий чеклист: когда использовать, когда не использовать, и какие грабли вас всё равно ждут.

Когда использовать

Это хорошо заходит, если у вас:

  • частые PR и релизы, где деградация «по чуть‑чуть» копится незаметно;

  • критичные по UX ручки/флоу (логин, поиск, корзина, платежи), которые хочется защищать так же, как функциональность;

  • есть staging/preprod, на котором можно стабильно гонять короткий smoke и хранить историю прогонов.

Лучше не тащить это в каждый PR, если:

  • нет стабильного окружения (стейджинг постоянно меняется, шумит, «соседи» убивают CPU);

  • нагрузка у вас нужна раз в квартал «проверить потолок», а не ловить регрессии;

Даже если вам «подходит по чеклисту», есть три грабли, от которых не убежит ни один пайплайн.

Ограничения и грабли

  • Locomotive не заменяет Locust. Если вам нужны сложные пользовательские сессии, динамические данные, нестандартные протоколы или хитрые stateful‑сценарии, вы по‑прежнему пишете locustfile.py, а Locomotive выступает «обвязкой качества» поверх него.

  • Locomotive — не распределённый генератор нагрузки. Если одной машины не хватает, масштабирование — это зона ответственности Locust (master/worker или --processes). [32]

  • Шум окружения никуда не девается. Стейджинг бывает капризным, соседний джоб в CI может сожрать весь CPU, сеть может подлагать. Как идея, можно поступить следующим образом: в PR — маленький стабильный смоук с мягкими правилами; в main — более жёсткие gates и, возможно, несколько прогонов с агрегацией. Впрочем, это уже вопрос стратегии, а не инструмента.

Заключение

Нагрузочные тесты перестают быть ритуалом по праздникам, когда у них появляется UX как у обычных тестов: запустил → получил вердикт → при необходимости открыл отчёт и понял, что именно поехало.

Начните с малого: один критичный флоу, короткий smoke на PR, мягкие правила (WARNING важнее, чем ложные фейлы). А дальше уже можно ужесточать пороги и наращивать регрессионную историю — когда пайплайн и окружение к этому готовы.

Ссылки