Нагрузочное тестирование API мобильного приложения
Мобильное приложение с 10 000 активных пользователей в пике — это не то же самое, что 10 000 одновременных HTTP-запросов. Но близко. Экраны загружаются параллельно, фоновая синхронизация WorkManager и BGTaskScheduler работает независимо от пользовательских действий, push-уведомление может одновременно поднять тысячи пользователей в приложение. Без нагрузочного тестирования API бэкенд ломается в самый неудобный момент.
Выбор инструмента
Три основных варианта, каждый со своей нишей:
| Инструмент | Язык сценариев | Сильная сторона |
|---|---|---|
| k6 | JavaScript | Современный, CI-friendly, низкий порог входа |
| JMeter | XML / GUI | Зрелый, богатые плагины, visual тест-дизайн |
| Gatling | Scala / DSL | Точные метрики, удобные HTML-отчёты |
Для большинства мобильных API выбираем k6 — компактные сценарии, нативная интеграция с Grafana, запуск в Docker без JVM.
Написание k6-сценария для мобильного API
Типичный мобильный API имеет особенности: JWT-авторизация с коротким TTL, refresh-токены, gzip-сжатие ответов, иногда GraphQL вместо REST. Сценарий должен это учитывать.
import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';
// Тестовые аккаунты из CSV — не один аккаунт для всех VU
const users = new SharedArray('users', () => open('./test_users.csv').split('\n').map(line => {
const [email, password] = line.split(',');
return { email, password };
}));
export const options = {
stages: [
{ duration: '2m', target: 100 }, // разгон
{ duration: '5m', target: 500 }, // плато
{ duration: '2m', target: 1000 }, // пик
{ duration: '1m', target: 0 }, // спад
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% запросов < 500ms
http_req_failed: ['rate<0.01'], // менее 1% ошибок
},
};
export default function () {
const user = users[__VU % users.length];
// Логин + получение токена
const loginRes = http.post('https://api.example.com/v1/auth/login', JSON.stringify({
email: user.email,
password: user.password,
}), {
headers: { 'Content-Type': 'application/json' },
});
check(loginRes, {
'login 200': (r) => r.status === 200,
'has token': (r) => r.json('data.access_token') !== undefined,
});
const token = loginRes.json('data.access_token');
sleep(1); // имитируем поведение пользователя
// Загрузка ленты
const feedRes = http.get('https://api.example.com/v1/feed?page=1&limit=20', {
headers: { Authorization: `Bearer ${token}` },
});
check(feedRes, {
'feed 200': (r) => r.status === 200,
'feed has items': (r) => r.json('data.items').length > 0,
});
sleep(2);
}
SharedArray для пользователей — критично. Если все Virtual Users используют один аккаунт, бэкенд может кэшировать сессию и результаты будут некорректными.
Профили нагрузки для мобильных приложений
Мобильный трафик неравномерен. Три типичных профиля:
Базовый — стабильная нагрузка 24/7. Проверяем, что при среднем трафике p95 < 300 ms.
Пиковый — утренний и вечерний всплеск. Ступенчатый рост от 10% до 300% среднего за 5 минут.
Стресс-тест — намеренно превышаем расчётный максимум, ищем точку отказа. До тех пор, пока error rate не превысит 5% или latency не вырастет в 10 раз.
Soak-тест — 70% от пикового в течение 4–8 часов. Ловит утечки памяти на бэкенде, переполнение пула соединений к БД, ротацию логов.
Анализ узких мест
k6 отправляет метрики в Grafana через InfluxDB или встроенный Prometheus remote write:
k6 run --out influxdb=http://localhost:8086/k6 scenario.js
После прогона смотрим на:
-
http_req_durationпо перцентилям (p50, p90, p95, p99) -
http_req_blocked— время в очереди (высокое значение = connection pool исчерпан) -
http_req_connecting— время установки TCP-соединения (высокое = нет keep-alive) -
data_received— объём данных (неожиданно большой = нет gzip или лишние поля в ответе)
Типичные узкие места в мобильном API:
- N+1 запросы к БД при загрузке ленты с вложенными объектами
- Отсутствие индекса на
user_id+created_atв таблице постов - Синхронная отправка push-уведомлений в теле запроса вместо фоновой очереди
Запуск в CI
- name: Run k6 load test
uses: grafana/[email protected]
with:
filename: tests/load/api_test.js
flags: --duration 5m --vus 100
env:
K6_CLOUD_TOKEN: ${{ secrets.K6_CLOUD_TOKEN }}
На CI запускаем облегчённый профиль (100 VU, 5 минут) — для базовой регрессии по производительности. Полный стресс-тест — по расписанию или перед релизом.
Сроки
3–5 дней — написание сценариев для основных эндпоинтов, настройка профилей нагрузки, первый прогон, анализ результатов и отчёт с рекомендациями. Стоимость рассчитывается индивидуально после изучения API-документации.







