SQL-запрос который выполняется за 30 мс на 100 строках может завалиться по таймауту на миллионе, а функциональные тесты это не поймают: они проверяют один сценарий за раз. Нагрузочное тестирование симулирует параллельный трафик который обнажает такие проблемы. Нагрузочное тестирование для нормального трафика, стресс-тестирование для поиска точки отказа, спайк-тестирование для внезапных всплесков и выдержка для выявления утечек памяти которые видны только через несколько часов. Гайд охватывает k6: инструмент на JavaScript который запускает нагрузочные тесты из CLI, возвращает ненулевой код при невыполнении порогов и встраивается в GitHub Actions.
Почему нагрузочное тестирование важно
Форма логина которая отвечает за 200 мс: нормально. За 5 секунд пользователь уходит. За 30 секунд под нагрузкой сервер падает.
Производительностные баги часто самые дорогие в исправлении: они требуют архитектурных изменений, не точечных правок. Найти их раньше принципиально важно.
Типичные проблемы производительности
- SQL-запросы которые работают нормально на 100 строках, падают по таймауту на миллионе
- API-эндпоинты которые справляются с 10 параллельными пользователями, ломаются на 100
- Утечки памяти которые накапливаются часами
- Сторонние интеграции которые становятся узким местом под нагрузкой
Виды нагрузочных тестов
Нагрузочное тестирование
Симулирует ожидаемый продакшн-трафик и проверяет что система укладывается в допустимые пределы.
Вопрос: справляется ли система с нормальным трафиком? Пример: в приложении 500 параллельных пользователей в рабочее время. Запускаем 500 виртуальных пользователей на 30 минут и измеряем время ответа.Приемлемый результат: 95-й перцентиль времени ответа меньше 1 секунды, процент ошибок меньше 1%, и нет утечек памяти.
Стресс-тестирование
Нагружаем систему сверх лимитов чтобы найти точку отказа.
Вопрос: как система ведёт себя при перегрузке? Падает ли она управляемо? Пример: начинаем со 100 пользователей, добавляем по 100 каждую минуту пока система не сломается. Смотрим когда начинаются ошибки, как система падает и восстанавливается ли она.Спайк-тестирование
Резкий скачок нагрузки и возврат к норме.
Вопрос: справляется ли система с внезапными всплесками трафика? Пример: нормальная нагрузка 100 пользователей. Резко прыгаем до 1000 на 2 минуты, затем возвращаемся к 100. Типичные причины всплесков: упоминание в соцсетях, статья в СМИ, флеш-продажа.Выдержка
Продолжительная нагрузка на длительный период.
Вопрос: есть ли утечки памяти или деградация производительности со временем? Пример: 200 параллельных пользователей 8 часов. Мониторим использование памяти и динамику времени ответа.k6: современный инструмент нагрузочного тестирования
k6: самый QA-friendly инструмент нагрузочного тестирования. JavaScript-синтаксис, запуск из CLI, интеграция с CI.
Установка
# macOS
brew install k6
# Windows (winget)
winget install k6
# Docker
docker pull grafana/k6Первый нагрузочный тест
// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
// Конфигурация теста
export const options = {
vus: 50, // Виртуальные пользователи
duration: '30s', // Длительность 30 секунд
};
export default function() {
// Запрос
const response = http.get('https://lab.becomeqa.com/api/products');
// Проверки
check(response, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
'has products': (r) => JSON.parse(r.body).length > 0,
});
// Пауза между итерациями (имитация реального пользователя)
sleep(1);
}Запуск:
k6 run load-test.jsВывод:
✓ status is 200 100.00% ✓ 1500 ✗ 0
✓ response time < 500ms 95.33% ✓ 1430 ✗ 70
✓ has products 100.00% ✓ 1500 ✗ 0
http_req_duration..............: avg=185ms min=45ms med=160ms max=1.2s p(90)=350ms p(95)=480ms
http_reqs......................: 1500 49.91/sСценарии с постепенным наращиванием нагрузки
Реалистичнее чем сразу давать полную нагрузку:
export const options = {
stages: [
{ duration: '2m', target: 100 }, // Наращиваем до 100 пользователей за 2 минуты
{ duration: '5m', target: 100 }, // Держим 100 пользователей 5 минут
{ duration: '2m', target: 200 }, // Наращиваем до 200
{ duration: '5m', target: 200 }, // Держим 200 пользователей
{ duration: '2m', target: 0 }, // Снижаем нагрузку
],
};Тестирование API с аутентификацией
import http from 'k6/http';
import { check, group } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 50 },
{ duration: '3m', target: 50 },
{ duration: '1m', target: 0 },
],
thresholds: {
'http_req_duration': ['p(95)<500'], // 95% запросов быстрее 500 мс
'http_req_failed': ['rate<0.01'], // Менее 1% ошибок
},
};
export function setup() {
// Логинимся один раз, токен для всех виртуальных пользователей
const res = http.post('https://api.myapp.com/auth/login', JSON.stringify({
email: 'load-test@myapp.com',
password: 'LoadTestPass1',
}), { headers: { 'Content-Type': 'application/json' } });
return { token: JSON.parse(res.body).token };
}
export default function(data) {
const headers = {
'Authorization': `Bearer ${data.token}`,
'Content-Type': 'application/json',
};
group('List products', () => {
const res = http.get('https://api.myapp.com/products', { headers });
check(res, { 'products loaded': (r) => r.status === 200 });
});
group('Get product detail', () => {
const res = http.get('https://api.myapp.com/products/1', { headers });
check(res, { 'product detail loaded': (r) => r.status === 200 });
});
sleep(Math.random() * 3 + 1); // Случайная пауза 1–4 секунды
}Пороговые значения
Задаём критерии прохождения/провала:
export const options = {
thresholds: {
// 95% запросов должны завершиться быстрее 500 мс
'http_req_duration': ['p(95)<500'],
// Порог для конкретного эндпоинта
'http_req_duration{name:products}': ['p(95)<300'],
// Допустимо менее 0.1% ошибок
'http_req_failed': ['rate<0.001'],
// Порог кастомной метрики
'checkout_duration': ['p(90)<2000'],
},
};Если пороги не выполнены, k6 завершается с ненулевым кодом: CI падает автоматически.
Что измерять
Перцентили времени ответа
- p50 (медиана): 50% запросов быстрее этого значения
- p90: 90% запросов быстрее этого значения
- p95: 95% запросов быстрее этого значения
- p99: медленный хвост, то что испытывает худший 1%
Интеграция с CI
# GitHub Actions
performance-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install k6
run: |
curl https://github.com/grafana/k6/releases/download/v0.50.0/k6-v0.50.0-linux-amd64.tar.gz -L | tar xvz
sudo mv k6-v0.50.0-linux-amd64/k6 /usr/local/bin
- name: Run load test
run: k6 run load-tests/api-load-test.js
env:
BASE_URL: ${{ secrets.STAGING_URL }}
- name: Upload results
uses: actions/upload-artifact@v4
if: always()
with:
name: k6-results
path: k6-results.jsonТипичные проблемы производительности
Медленные SQL-запросы
Отслеживай время выполнения запросов под нагрузкой, проверяй на N+1 запросы (1 запрос на строку вместо одного общего) и ищи отсутствующие индексы.
Утечки памяти
Использование памяти растёт со временем без снижения. Видно в тестах на выдержку.
Исчерпание пула соединений
Соединения с базой данных заканчиваются при высокой параллельности. Типичная ошибка: «too many connections».
Узкие места в сторонних сервисах
Внешний API который тормозит под нагрузкой, или платёжный шлюз с ограничениями частоты запросов.
Отсутствующее кэширование
Одни и те же данные повторно тянутся из БД вместо кэша.
Итоговая таблица
| Тип теста | Цель | Длительность |
|-----------|------|--------------|
| Нагрузочный | Проверка производительности при нормальной нагрузке | 30 мин – 2 ч |
| Стресс | Найти точку отказа | До сбоя |
| Спайк | Обработка внезапных всплесков трафика | Короткие всплески |
| Выдержка | Обнаружение утечек памяти | Часы |
Основы k6
vus: виртуальные пользователиduration: длительность запускаstages: паттерны наращивания нагрузкиthresholds: критерии прохождения/провалаcheck(): проверки на уровне запроса
Нагрузочное тестирование отличается от функционального: разные инструменты, разные цели. Функциональные тесты проверяют корректность, нагрузочные проверяют скорость и надёжность под нагрузкой. Нужны и те и другие перед релизом.
→ See also: Нагрузочное тестирование с k6: первый нагрузочный тест QA-инженера | Пирамида тестирования: объяснение для QA-инженеров | API-тестирование 101: всё, что нужно знать QA-инженеру в 2026 году