Перейти к содержимому

Нагрузочное тестирование: финансовый аудит архитектуры

Константин Потапов
12 мин

Методология и алгоритм: как считать деньги на нагрузке, выбирать инструмент и превращать результаты в решения.

Нагрузочное тестирование: финансовый аудит архитектуры

Когда проблема приходит с трафиком

Пока вы читаете это предложение, ваш код, возможно, теряет деньги — не из-за багов, а из-за непредусмотренной нагрузки. У коллег был кейс: реклама на 500k ₽ в день, прод лежал полдня — минус ~250k ₽ на ровном месте. С тех пор нагрузочное тестирование — это финансовый аудит архитектуры: ищем точку, где система начинает съедать выручку, и считаем, сколько это стоит.

Помните, как маркетплейсы и ритейл ложились на Чёрную пятницу или 11.11? Или как порталы госуслуг и записи к врачу не выдерживали наплыв в первый день запусков QR-пропусков и вакцинации? Их ошибка была не в том, что они не тестировали, а в том, что тестировали не то и не так.

Задержка в 1 секунду при 1000 RPS — это 1000 секунд совокупного ожидания пользователей каждую секунду реального времени. Это прямые брошенные корзины и негативные отзывы. Нагрузочное тестирование — это способ заранее знать, на каком RPS и из-за какого компонента вы начинаете терять деньги и сколько именно.

90% проблем с производительностью видно только под нагрузкой. Локальный curl даст 50ms, а под 1000 RPS вы получите секунды ожидания и 500-е.

Что такое нагрузочное тестирование простыми словами

Держу в голове два вопроса, когда запускаю нагрузку:

  • Performance testing: насколько система быстрая при целевой нагрузке? Это про p95, latency, throughput.
  • Resilience testing: как и когда система ломается, как она деградирует и восстанавливается, и сколько это стоит? Это про пределы и точки отказа в деньгах.

Нагрузочное тестирование — всегда про устойчивость и деньги. Мы ищем, где система треснет, и считаем, сколько это будет стоить бизнесу.

Типы тестов (все переводим на язык денег):

  • Load testing — "Сколько пользователей мы обслужим, не начав терять деньги?"
  • Stress testing — "Где именно и при какой нагрузке у нас прогорит дно, и во сколько это обойдётся?"
  • Spike testing — "Если прилетит хабр-эффект/топовый блогер, при каком RPS мы падаем и сколько теряем?"
  • Soak testing — "Сколько стоит деградация или утечка памяти после часа пика?"

Инструменты: какие использую и почему

Выбор инструмента вторичен. Главное — отвечать на бизнес-вопросы, а не рисовать красивые графики. Мы используем k6, потому что его скрипты — код в репозитории, который живёт в CI/CD и дешёв в поддержке. Вы можете взять Gatling или JMeter — важнее, чтобы результаты тестов отвечали на вопрос "сколько денег мы теряем при текущей архитектуре?".

Принципы выбора инструмента:

  • Тест — это код: лежит рядом с продуктовым кодом и ревьюится.
  • CI/CD: один шаг для запуска, без ручных кликов.
  • Бизнес-метрики: простые пороги, кастомные метрики и привязка к деньгам.
  • Прозрачность: отчёты понимают разработчики, продакты и менеджмент.
Принцип / Инструментk6Apache JMeterGatling
Тест как код✅ JS/TS❌ XML/GUI✅ Scala DSL
Интеграция в CI/CD✅ Из коробки⚠️ Громоздко✅ Отлично
Бизнес-метрики✅ Пороги, кастомные метрики⚠️ Есть, но сложнее✅ Богатые отчёты
Кому подходитDevOps, стартапыQA, унаследованные проектыJVM-команды, высокие нагрузки

Мы выбрали k6, потому что он полностью закрывает эти принципы. Любой другой инструмент должен соответствовать тем же критериям.

k6 — мой основной стек

Использую k6 как дефолт: быстрый старт, прозрачные метрики, легко автоматизировать.

Почему k6:

  • Скрипты на JavaScript/TypeScript — привычный синтаксис, быстрая разработка
  • Встроенные метрики: response time, RPS, error rate
  • Интеграция с Grafana Cloud или локальным стеком
  • CLI-first: легко в CI/CD
  • Open source и бесплатный

Почему k6 — это не просто инструмент, а смена парадигмы

JMeter и его наследники рождались для отдельных QA-команд и GUI-конфигов. k6 — инструмент эпохи DevOps, где разработчик и инженер — одно лицо: JavaScript вместо XML, код вместо кликов, CI/CD вместо ручных запусков. Выбор k6 — решение в пользу того, чтобы нагрузочные тесты стали частью кодовой базы, а не артефактами сбоку.

k6 заставляет думать о тестах как о коде: код можно ревьюить, тестировать и поддерживать. Это меняет культуру команды.

k6GrafanaInfluxDBDocker

Технические примеры и скрипты держу в репозитории: это код, который ревьюим и прогоняем в CI. Здесь важна методология, а не синтаксис.

Альтернативы (когда и зачем)

Если k6 не заходит, беру инструмент под команду и контекст:

Apache JMeter:

  • GUI для визуальной настройки (если команда не JS)
  • Зрелая экосистема плагинов
  • Минусы: тяжелее, XML-конфиги, сложнее в CI

Gatling:

  • Scala DSL (для JVM-команд)
  • Красивые отчёты из коробки
  • Хорош для сложных сценариев с состоянием

Locust:

  • Python скрипты
  • Распределённая нагрузка
  • Хорош, если стек уже на Python

Artillery:

  • YAML-конфиги (проще для простых кейсов)
  • Встроенная поддержка WebSocket, Socket.io
  • Минусы: менее гибкий для сложных сценариев

k6 закрывает 90% кейсов: знакомый JS, удобная CLI, запуск в Docker и CI, бесплатная отправка метрик в Grafana Cloud.

Стальной алгоритм (с нуля)

  1. Бизнес-анализ: посчитать стоимость 1% ошибок и 1 секунды latency для ключевого сценария.
  2. Инструмент: выбрать стек (k6/Gatling/JMeter) по принципам "тест как код", CI/CD, метрики, прозрачность.
  3. Сценарии: описать 2–3 денежно-критичных flow, зафиксировать гипотезы "где сломается".
  4. Прогон: профиль нагрузки с плавным ростом, пороги SLA, запуск в CI/CD.
  5. Расследование: сопоставить метрики теста и инфраструктуры, найти корень проблемы.
  6. Отчёт: гипотеза → данные → действие → верификация, привязка к деньгам.

Методология: шаг 0 и 5 шагов к значимым результатам

Гонка нагрузки ради красивых графиков — пустая трата времени. У каждого шага есть бизнес-вопрос, на который отвечаем цифрами и действиями.

Красная нить примера: сервис такси, сценарий "поиск → заказ → оплата". Мы знаем из аналитики, что при p95 > 3s на поиске машин конверсия падает на 15%. При пиковой нагрузке 1000 RPS это 150 потерянных запросов в секунду. При средней стоимости заказа $20 и конверсии 10% в заказ это ~$300 потерянной выручки в минуту. Нагрузочный тест показал, что геокодер трескается при 800 RPS — значит, потенциальный убыток ~$2400 в час пик, пока мы не починим геокодер.

0. Формулирую гипотезы

Перед запуском теста команда письменно отвечает: где система сломается первой при росте нагрузки в N раз? Примеры: "БД упрётся в IOPS на 300 RPS", "кеш исчерпается на 500 VU", "внешний платежный шлюз начнёт отдавать таймауты". Эти гипотезы потом проверяем тестом.

1. Определяю сценарии

Выбираю 2–3 критичных user flow, остальное не трогаю в первом прогоне. Бизнес-вопрос: какие 3 сценария дают 80% выручки или создают пик нагрузки?

  • Авторизация + просмотр дашборда
  • Поиск товара + добавление в корзину + чекаут
  • Создание записи + список записей

Пример сценария:

import http from "k6/http";
import { group, check } from "k6";
 
export default function () {
  group("User Journey: Login → Dashboard → Logout", () => {
    // 1. Логин
    const loginRes = http.post("https://api.example.com/auth/login", {
      email: "test@example.com",
      password: "password123",
    });
 
    check(loginRes, { "login success": (r) => r.status === 200 });
    const token = loginRes.json("token");
 
    // 2. Получение дашборда
    const headers = { Authorization: `Bearer ${token}` };
    const dashRes = http.get("https://api.example.com/dashboard", { headers });
 
    check(dashRes, { "dashboard loaded": (r) => r.status === 200 });
 
    // 3. Логаут
    http.post("https://api.example.com/auth/logout", null, { headers });
  });
}

2. Настраиваю профиль нагрузки

Наращиваю нагрузку ступенями: включить 10000 VU сразу — получить ложные падения. Бизнес-вопрос: при каком росте трафика метрики начинают стоить нам денег?

export const options = {
  stages: [
    { duration: "1m", target: 50 }, // Прогрев
    { duration: "3m", target: 50 }, // Baseline
    { duration: "2m", target: 150 }, // Рост (пик часы)
    { duration: "5m", target: 150 }, // Пиковая нагрузка
    { duration: "2m", target: 300 }, // Стресс-тест
    { duration: "3m", target: 300 }, // Удержание стресса
    { duration: "2m", target: 0 }, // Плавный спуск
  ],
};

3. Задаю пороги (thresholds)

Пороги превращают прогон в проверку SLA, а не просто цифры ради цифр. Бизнес-вопрос: какое p95 latency — порог рентабельности для ключевого сценария?

export const options = {
  thresholds: {
    // Latency
    http_req_duration: [
      "p(50)<200", // Медиана < 200ms
      "p(95)<500", // 95 перцентиль < 500ms
      "p(99)<1000", // 99 перцентиль < 1s
    ],
 
    // Доступность
    http_req_failed: ["rate<0.01"], // < 1% ошибок
 
    // Throughput
    http_reqs: ["rate>100"], // Минимум 100 RPS
 
    // Кастомные проверки
    "checks{type:auth}": ["rate>0.99"], // 99% успешных логинов
  },
};

4. Запускаю тест

Запускаю в CI/CD, чтобы один и тот же сценарий гонялся в одинаковых условиях. Локально — только для отладки.

5. Анализирую результаты

Сначала смотрю метрики прогона. Бизнес-вопрос: сколько денег мы теряем при текущих метриках и где именно?

  • Response time percentiles (p50, p95, p99)
  • Error rate (по кодам: 4xx vs 5xx)
  • Throughput (RPS)
  • Virtual Users (сколько выдержали)

Параллельно сверяю с инфраструктурой:

  • CPU/Memory на серверах (через Prometheus/Grafana)
  • Database queries per second, slow queries
  • Cache hit rate
  • Network I/O

Если p95 < 500ms, а p99 = 5s — хвосты длинные. Ищите медленные запросы в БД или таймауты внешних API.

Ключевые метрики для решений

Визуализация, которая обязана быть: график latency vs throughput (ровная линия в идеале, взлёт в реальности) и треугольник "latency — error rate — throughput", плюс графики насыщения ресурсов (CPU/IO/сеть) на одном экране Grafana.

p95
Response Time
< 1%
Error Rate
RPS
Throughput
Apdex
User Satisfaction

Latency (p95/p99):

  • p95 — ваш SLA для основной массы пользователей.
  • p99 — ваша совесть: если p95 = 200ms, а p99 = 2000ms, у вас "ядро и хвост". Хвост — системная проблема: медленные запросы к БД, блокировки, GC.

Error Rate:

  • 1% ошибок при 1000 RPS — это 10 неудачных запросов в секунду.
  • За 10 минут теста это 6000 ошибок — это инцидент, а не статистическая погрешность.

Throughput (RPS):

  • Сам по себе бесполезен; ценность — в корреляции с latency.
  • Смотрите график "Latency vs Throughput": линия должна быть пологой до предела.
  • Если latency взлетает при росте RPS — нашли потолок пропускной способности узкого места.

Resource Saturation:

  • CPU, диск, сеть, коннекты к БД. RPS при 90–100% CPU или IOPS — сигнал, что треснет инфраструктура, а не приложение.
  • Коррелируйте пики latency с пиками использования ресурсов.

Apdex (Application Performance Index):

  • Сжатая метрика удовлетворённости для бизнеса.
  • Настройте S/T/F под свой продукт, иначе цифра теряет смысл.
  • Формула: (Satisfied + Tolerating/2) / Total. Например, Satisfied < 200ms, Tolerating до 800ms, Frustrated — всё медленнее. Для трейдинговой платформы Satisfied ближе к 50ms, для CMS — к 500ms.

Инфраструктурные (корреляция)

  • CPU Usage — > 80% длительно → узкое место
  • Memory — растёт линейно → утечка памяти
  • Database connections — исчерпан пул → тюним
  • Cache hit rate — низкий → плохая стратегия кеширования

Как я ищу корень проблемы (коротко)

  • Высокий p99 + пики CPU/IO на БД → смотрю slow query log, EXPLAIN ANALYZE, pt-query-digest.
  • Ошибки 5xx без роста CPU → проверяю лимиты подключений к БД, пулы в приложении, таймауты внешних API.
  • Throughput упёрся, CPU низкий → ищу глобальные блокировки, проблемы сети или лимиты rate limiting.
  • RPS растёт, latency скачет, cache hit падает → ловлю промахи кеша и пересматриваю TTL/стратегию.

Отчёт: как превратить цифры в действия

Это сердце подхода: отчёт сразу превращается в backlog действий и гипотез.

Структура отчёта (мой шаблон)

Использую такой шаблон, чтобы отчёт сразу превращался в backlog действий:

# Нагрузочный тест: API v2.0
 
## Цель
 
Проверить готовность API к увеличению нагрузки в 3x (с 500 до 1500 RPS).
 
## Сценарий
 
- User Journey: Login → Get Dashboard → Logout
- Профиль нагрузки: 50 → 150 → 300 VU
- Длительность: 18 минут
 
## Результаты
 
### ✅ Прошли пороги
 
- p95 latency: 420ms (порог: < 500ms)
- Error rate: 0.3% (порог: < 1%)
- Throughput: 1200 RPS (ожидалось: 1000 RPS)
 
### ❌ Проблемы
 
- p99 latency: 3.2s (порог: < 1s)
- CPU на DB: 92% на пике
- Slow queries: `/users` эндпоинт → N+1 запросы
 
## Узкие места (по приоритету)
 
1. **Database N+1 queries**`/users` делает 50+ запросов к БД
   - **Действие:** добавить eager loading
   - **ETA:** 2 дня
   - **Эффект:** p99 latency → < 800ms
 
2. **CPU на DB** — достигает 92% на 300 VU
   - **Действие:** увеличить инстанс (4 → 8 vCPU)
   - **ETA:** 1 день
   - **Эффект:** запас до 500 VU
 
3. **Cache hit rate** — только 65% для `/dashboard`
   - **Действие:** увеличить TTL кеша с 5 мин до 15 мин
   - **ETA:** 1 день
   - **Эффект:** снизить нагрузку на DB на 20%
 
## Рекомендации
 
- Оптимизировать N+1 queries **критично перед релизом**
- Увеличить DB инстанс **желательно**
- Настроить кеш **можно отложить**
 
## Гипотеза и верификация (обязательный блок для каждой проблемы)
 
- **Проблема:** p99 latency: 3.2s на `/users`
- **Гипотеза:** в логах БД видно N+1 запросов на этот эндпоинт
- **Действие:** добавить eager loading
- **Верификация:** повторить тот же тест и подтвердить p99 < 800ms
 
## Графики
 
[Attach Grafana dashboard screenshot]
 
## Next Steps
 
- [ ] Фикс N+1 queries
- [ ] Повторный тест после оптимизации
- [ ] Stress test до поиска предела (500+ VU)

Отчёт должен отвечать на вопрос: «Что делать?», а не «Что было?». Цифры без действий — бесполезны.

Типовые грабли

0. Тестируете не ту систему

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

Решение: тестовое окружение должно быть аппаратно и программно идентично продакшену. Данные — репрезентативные. Все внешние зависимости должны быть либо подняты в тестовом контуре, либо использовать prod-версии (с согласия владельцев).

Анти-пример отчёта

Типичный "отчёт", который не отвечает ни на один бизнес-вопрос:

  • Провели нагрузочное тестирование
  • Достигли 5000 RPS
  • p95 = 2.3s
  • Рекомендация: "оптимизировать БД"

Почему бесполезно:

  • Неясно, можем ли мы работать при 5000 RPS (p95 = 2.3s — уже нет).
  • Неясно, что именно оптимизировать в БД.
  • Неясно, стоят ли оптимизации затрат.

Нужен ответ: при каком RPS мы укладываемся в p95 SLA, сколько денег теряем выше этого порога и какой компонент ломается первым.

1. Тест на другом железе

Проблема: staging слабее production, цифры ничего не значат.

Решение: гоняйте prod в off-peak или выровняйте ресурсы staging под прод.

2. Rate limiter душит, а не нагрузка

Проблема: упираетесь в лимиты (Cloudflare, Nginx), а не в код.

Решение: отключите или поднимите лимиты для IP раннера.

3. Нет прогрева

Проблема: первые запросы медленные из-за холодного кеша и JIT.

Решение: добавьте warm-up стадию перед основным тестом:

export const options = {
  stages: [
    { duration: "1m", target: 10 }, // Warm-up
    { duration: "5m", target: 100 }, // Основной тест
  ],
};

4. Нет инфраструктурных метрик

Проблема: k6 зелёный, а сервер всё равно падает.

Решение: смотрите CPU, память, диск, сеть параллельно с k6 метриками.

5. Один огромный прогон

Проблема: тест на 2 часа, непонятно, в какой момент всё сломалось.

Решение: разбейте на микротесты:

  • Baseline: 50 VU, 5 мин
  • Peak load: 150 VU, 10 мин
  • Stress: 300 VU, 5 мин

Интеграция в CI/CD

GitLab CI пример

load-test:
  stage: test
  image: grafana/k6:latest
  script:
    - k6 run --out json=results.json tests/load/api.js
  artifacts:
    reports:
      junit: results.json
  only:
    - main
  when: manual # Запускаем вручную перед релизом

GitHub Actions пример

name: Load Test
 
on:
  workflow_dispatch: # Ручной запуск
 
jobs:
  load-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
 
      - name: Run k6 test
        uses: grafana/k6-action@v0.3.1
        with:
          filename: tests/load/api.js
          cloud: true
          token: ${{ secrets.K6_CLOUD_TOKEN }}

Не запускайте нагрузочные тесты на каждый коммит — это дорого и медленно. Запускайте перед релизами или по расписанию (nightly).

Бизнес-инсайты

Нагрузочные тесты — это про деньги и репутацию, не только про графики.

ROI нагрузочного тестирования

Без тестов
С нагрузочными тестами
Время на тест
0 часов (не делали)
8 часов (настройка + прогон)
Downtime в production
4 часа при пике
0 часов
100%
Потери выручки
$50k (простой чекаута)
$0
100%
Репутация
Негативные отзывы
Стабильный сервис

Экономика простая:

  • Стоимость теста: 1–2 дня работы инженера (~$500–1000)
  • Стоимость падения в прод: часы простоя × потери выручки (~$10k–100k+)
  • ROI: 10x–100x

Формула стоимости инцидента:

(Средний чек × Конверсия × Доля затронутого трафика × Длительность сбоя) + (Стоимость поддержки × Количество тикетов) + (Стоимость репутации × Коэффициент потерь)

Нагрузочное тестирование позволяет подставить реальные цифры: "при 300 RPS и 10% ошибок в чекауте мы теряем X рублей в минуту".

Когда нагрузочные тесты критичны

  • E-commerce — распродажи, чёрная пятница
  • Стриминг — запуск нового контента
  • Финтех — выплаты, массовые транзакции
  • SaaS — вирусный рост, упоминание в прессе

Вопросы, которые задаёт CEO

  • Какой процент ошибок мы готовы терпеть и сколько это в абсолютных запросах в день?
  • При каком p95 конверсия падает и на сколько это денег в час?
  • Дешевле ли поднять архитектуру или заложить 0.5% потерь в бизнес-план?

Вывод

Нагрузочное тестирование — не пункт чек-листа перед релизом, а практика тестирования устойчивости, которая напрямую влияет на деньги. Это способ превратить непредсказуемый инцидент в запланированную задачу по оптимизации. В каждом прогоне вы отвечаете на вопрос: сколько денег мы теряем при текущей архитектуре и что нужно сделать, чтобы перестать их терять?

P.S. Первый серьёзный нагрузочный тест почти наверняка покажет 2–3 узких места, о которых вы не подозревали. Пусть об этом узнаете вы, а не ваши пользователи.

С понедельника сделайте один шаг:

  • выберите один бизнес-критичный сценарий;
  • посчитайте, сколько денег вы теряете при 1% ошибок или p95 > 1s;
  • запустите тест, который покажет, при какой нагрузке вы достигаете этих порогов.

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

См. также: