Десятая статья серии «Архитектура и производительность». К этому моменту у нас сложилась почти полная картина: сервисы разделили или оставили монолитом (монолит против микросервисов), договорились об интерфейсах (проектирование API), разгрузили тяжёлые задачи (очереди сообщений), ускорили ответы (кеширование), раскидали запросы по серверам (балансировка нагрузки), научились смотреть на цифры (метрики и мониторинг), читать логи (логирование и трассировка), подтянули базу (оптимизация баз данных) и вынесли статику на край сети (CDN и edge-вычисления). Но одна вещь по-прежнему остаётся за кадром: что будет, когда трафик вырастет в десять раз.
Нагрузочное тестирование — это способ проверить свой сайт не «в среднем за день», а в худший час худшего дня. Один раз потратив пару часов на прогон, вы заранее узнаёте, где система начнёт хрипеть, какие индексы не выдержат и хватит ли вам трёх бэкендов. Дальше — про инструменты (k6, JMeter, wrk), про типы тестов (эталонный замер, стресс, долгая нагрузка, имитация поломок), про то, как читать цифры под нагрузкой, как Kubernetes сам добавляет копии сервиса, и как встроить проверки в конвейер сборки (CI/CD). Без абстракций: с готовыми скриптами, командами и конфигурациями.
Часть I. Зачем нагружать свой сайт заранее
1. Пользователи — плохой тестировщик
Типичный сценарий: сайт работал полгода на одном VPS, ничего не мешало. Потом вышла статья в подборке, трафик вырос в десять раз, через час началась каша — страницы открываются, база захлёбывается, часть запросов упирается в «502 Bad Gateway». Через два часа волна спадает, всё возвращается в норму, и кажется, что ничего не случилось. Случилось: вы потеряли десятки тысяч посетителей, а половина из них не вернётся.
Проблема в том, что реальный пиковый момент нельзя воспроизвести снова. Когда он приходит в первый раз, вы заняты тушением пожара и не можете спокойно посмотреть, где узкое место. Когда он приходит второй раз, вы обычно уже купили в два раза больше серверов «на всякий случай» и переплачиваете. Нагрузочное тестирование — способ поймать пики заранее, на своей машине, в лабораторных условиях, где никто не уходит и никто не жалуется.
2. Что именно даёт тест
- Понимание пределов. Сколько запросов в секунду (RPS, requests per second) выдерживает конкретная сборка при заданном времени ответа. Это не «примерно много», а конкретная цифра, которую можно записать в план.
- Поиск узкого места. Тест отвечает на вопрос «где именно”: CPU упёрся в 100%, MySQL на диске ждёт fsync, сеть кончилась по пропускной способности, PHP-FPM исчерпал пул воркеров. Лечить надо только то, что упёрлось.
- Защита от регресса. Вы добавили колонку в большую таблицу без индекса, ничего не заметили на пяти страницах — и через неделю на полном трафике запрос стал грузиться пять секунд. Регулярный тест ловит это на коммите, а не в проде.
- Спокойный сон при росте. Перед крупным запуском, распродажей, появлением в подборке вы заранее понимаете, сколько копий сервиса надо добавить и где надо подкрутить балансировщик.
3. Честно о цене теста
Есть соблазн «провести нагрузочный тест» как ритуал: запустить инструмент на одну страницу и получить красивую цифру. Это почти всегда бесполезно. Чтобы тест имел смысл, надо вложить в него время:
- Собрать реальные пути, по которым ходят люди (главная, карточка, поиск, оплата) — не только
/. - Смоделировать данные: у пользователя есть cookies, у покупателя есть товар в корзине, у администратора — права.
- Запустить с тем же конфигом и той же версией приложения, что будут в проде, и желательно на машине сопоставимой мощности.
- Смотреть на метрики, а не только на цифры инструмента.
Если всё это игнорировать, тест покажет красивое «5000 запросов в секунду» и вы решите, что готовы. И потом разобьётесь о первую же страницу, которая реально тормозит.
Часть II. Инструменты: k6, JMeter, wrk
4. Три инструмента — три задачи
wrk — компактная консольная программа, которая умеет гонять HTTP-запросы с огромной скоростью и коротко показывать сводку: RPS, задержки, ошибки. Написан на C, использует epoll и несколько потоков. Ограничение — он не делает сложных сценариев: не залогинится, не выполнит запрос к одной странице, а потом к другой, не передаст токен. wrk идеален, когда нужно быстро померить «сырую» пропускную способность конкретного эндпоинта.
JMeter — старейший из тройки, Java-приложение с графическим интерфейсом. Строит планы тестирования из «тред-групп», «сэмплеров», «конфигураторов», «слушателей». Умеет практически всё: HTTP, JDBC к базам, FTP, сообщения в брокерах. Нагрузка моделируется блоками, но не скриптом, а деревом в интерфейсе. Жрёт память — Java, тяжел на машине, которая сама создаёт нагрузку, и сложен для команды, которая привыкла к коду.
k6 — современный инструмент от Grafana Labs. Сценарий пишется на JavaScript (ES-модули), исполняется в встроенном движке Go, поэтому держит высокий RPS. Внутри — привычные вещи программиста: функции, экспорты, переменные, проверка (check) статуса ответа, пороги (thresholds). Удобен для профилирования, смешивания путей и интеграции в CI. Минус — чуть меньше экзотики вокруг, чем у JMeter (FTP/SMTP/IMAP).
5. Сравнение возможностей
| Что сравниваем | wrk | JMeter | k6 |
|---|---|---|---|
| Сложность сценария | один URL, заголовки | дерево компонентов в GUI | JavaScript-функции |
| Логин / cookies / токен | нет (или через --header) | есть | есть, через cookie_jar |
| Скорость на одну машину | очень высокая | средняя (JVM) | высокая |
| Кривые задержки (percentiles) | p50/p90/p99 в консоли | через слушатели | через thresholds |
| Пороги провала | руками анализировать | через Assertion | прямо в скрипте |
| Запуск из CI | тривиально | тяжело (headless-режим) | тривиально |
| Порождаемое распределение | только RPS | GUI «Thread Group» с stages | stages и scenarios |
| Русский комьюнити | есть | есть | активно растёт |
| Стоимость | открытое ПО | Apache License 2.0 | AGPL (open-source-часть) |
6. Пример скрипта k6: профиль нагрузки
k6 хорош тем, что в одном файле описывается и «что делать», и «как быстро наращивать». Ниже — типичный сценарий: за 2 минуты набираем 200 «виртуальных пользователей» (VU), держим минуту, затем спускаемся. Внутри — два запроса: страница товара и подтверждение заказа через API. Проверяем статус и скорость через thresholds.
// load.js — профиль нагрузки для интернет-магазина
// запуск: k6 run load.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { CookieJar } from 'k6/http/cookie-jar';
export const options = {
stages: [
{ duration: '30s', target: 50 }, // разгон
{ duration: '60s', target: 200 }, // выход на пик
{ duration: '60s', target: 200 }, // плато
{ duration: '30s', target: 0 }, // спуск
],
thresholds: {
http_req_duration: ['p(95)<800'], // 95% ответов быстрее 800 мс
http_req_failed: ['rate<0.01'], // не больше 1% ошибок
checks: ['rate>0.99'], // проверки проходят почти всегда
},
};
const BASE = 'https://shop.example.test';
const jar = new CookieJar();
export default function () {
// 1. Карточка товара — самая тяжёлая страница
const product = http.get(`${BASE}/product/42`, { cookies: jar });
check(product, {
'открылась карточка': (r) => r.status === 200,
'ответ быстрее секунды': (r) => r.timings.duration < 1000,
});
// «думает» как живой пользователь
sleep(Math.random() * 4 + 1);
// 2. Добавить в корзину — POST с токеном, полученным из cookie
const res = http.post(
`${BASE}/api/cart/add`,
JSON.stringify({ sku: '42', qty: 1 }),
{
headers: { 'Content-Type': 'application/json' },
cookies: jar,
},
);
check(res, { 'корзина приняла': (r) => r.status === 201 });
sleep(1);
}
Ключевые места здесь:
stages— форма «волны»: плавный набор, плато, спуск. Именно так ведёт себя реальный магазин, а не 200 одновременных VU с первой секунды.thresholds— пороги, по которым решается «успех/провал». При их нарушении k6 вернёт ненулевой код выхода — CI поймает это автоматически.sleep(Math.random() * 4 + 1)— «думает» между запросами. Без паузы виртуальный пользователь будет долбить сервер без остановки, что в разы сильнее реального трафика.CookieJar— хранит cookies и передаёт их дальше. Без этого не работает ни одна сессия, ни один токен CSRF.
7. Пример запуска wrk
wrk запускается одной командой и почти не настраивается. Стандартный прогон: держим 30 секунд, четыре потока, по 250 соединений на поток. Если нужен «сценарий» с заголовками или логикой — подключается Lua-скрипт, но это уже территория, где k6 удобнее.
# Быстрый прогон по одному URL: 4 потока, 250 соединений, 30 секунд
wrk -t4 -c250 -d30s --latency https://shop.example.test/product/42
# С заголовком авторизации
wrk -t4 -c100 -d60s --latency \
-H "Authorization: Bearer ${API_TOKEN}" \
https://api.example.test/v1/orders
# Lua-скрипт для простых вариаций (разные query-параметры)
wrk -t4 -c200 -d60s --latency \
-s rotate_query.lua \
https://shop.example.test/search
# Сравнение до и после правки — одинаковый прогон, разные сборки
wrk -t4 -c250 -d30s --latency http://127.0.0.1:8080/ > baseline.txt
# ... деплой новой версии ...
wrk -t4 -c250 -d30s --latency http://127.0.0.1:8080/ > candidate.txt
diff baseline.txt candidate.txt
Важный нюанс: если wrk сам загружает машину, на которой крутится, результат искажается. На слабом ноутбуке 4 потока с 250 соединениями могут упереться в сеть или CPU клиента. Тогда вы измеряете не сервер, а свой ноутбук. Правильно: генератор нагрузки на отдельной машине (или в отдельном контейнере), а на сервере — только то, что нагружается.
8. JMeter: когда он всё-таки нужен
JMeter выбирают, когда сценарий выглядит как «зайти, пройти три шага, получить счёт из базы, отправить в брокер». Он умеет работать с JDBC, JMS, FTP, LDAP и GUI. В корпоративных командах, где тестировщик не программист, JMeter часто единственный инструмент, который ему понятен. Для нас это пограничный случай: если сценарий пишется на JavaScript в k6 за один файл — JMeter избыточен. Если же нужен интеграционный тест с реальным банком и брокером — там его графическая модель удобнее.
Часть III. Типы нагрузочных тестов
9. Benchmark — эталонная точка отсчёта
Benchmark (эталонный замер) — это «замерить, как быстро работает сегодня, чтобы с чем-то сравнивать завтра». Нагрузка маленькая, форма ровная, повторяемость идеальная. Один URL, один параметр, несколько прогонов, усреднение.
На практике benchmark полезен в трёх случаях:
- Сравнение сборок. «После обновления PHP с 8.2 до 8.3 стало быстрее или медленнее?» — сравниваются два замера.
- Сравнение железа. «Что дешевле: держать два маленьких VPS или один большой?»
- Сравнение реализаций. «Что быстрее: отдавать страницу через шаблон PHP или через кеш на диске?»
Чтобы бенчмарк имел смысл, надо убрать источники шума: другие процессы на машине, фоновые обновления, кеш браузера. Один и тот же тест, запущенный утром и вечером, даст разный ответ — это нормально, если разница 5%. Если 50% — у вас «плавающая» среда, надо копать в неё, а не в приложение.
10. Stress test — проверка на пределе
Stress test (стресс-тест) отвечает на вопрос «где вы ломаетесь?». Нагрузка растёт не до комфортного уровня, а до появления ошибок. Важно понять не «сколько выдержали», а «как именно вы умерли».
Хорошая смерть системы выглядит так: задержки растут, часть запросов падает с таймаутом, но ничего не теряет данных. Плохая смерть: в какой-то момент сервис «захлёбывается» — соединения висят открытыми, база блокирует таблицы, очередь уведомлений перестаёт писаться, а после снятия нагрузки система не возвращается сама.
Практика стресс-теста:
- Начинаем с комфортного уровня, например 50 VU.
- Добавляем по 50 VU каждые две минуты.
- После каждого шага смотрим на метрики и на ответы: где появились 5xx, где просела задержка, где кончились воркеры PHP-FPM.
- После провала — уменьшаем, проверяем, что система «отпустило». Если нет — это отдельная проблема, её надо чинить первой.
11. Soak test — длительная нагрузка
Soak test (от soak — «вымачивание») — это когда система живёт под нормальной нагрузкой много часов подряд, обычно 6–12, иногда сутки. Цель — поймать то, что не видно на коротком прогоне: утечки памяти, накопление мусора в сборщике, переполнение пула соединений, «ползущие» временные файлы, фрагментация индексов.
Типичная история, которую ловит soak:
PHP-FPM работает нормально на тесте в 30 секунд. На 40-й минуте — 40% памяти. На 3-м часу — 90% и система начинает своппить, задержки падают на нет.
Причина: какой-то модуль держит большой массив в статической переменной и не чистит. Короткий тест такое не заметит — память кончается не сразу.
Soak требует дисциплины: мониторинг должен работать (см. метрики и мониторинг). Без графиков за 12 часов вы увидите только итог: упало/не упало, и не поймёте, что именно ползло всё это время.
12. Chaos testing — имитация поломок
Chaos test (от хаос) — не «нагрузка», а «поломка». Идея в том, чтобы ронять отдельные части системы специально и проверять, что всё остальное выживает. Термин пришёл от практики Netflix: «Chaos Monkey» специально вырубал серверы в проде, чтобы команда не забывала о resilience.
Примеры хаот-проверок для PHP/WordPress-проекта на VPS или в Kubernetes:
- Убить один бэкенд. Балансировщик должен вывести его из пула (см. балансировка нагрузки), запросы должны перелечь на остальные, пользователи почти не заметить.
- Поднять задержку сети между сервисом и базой до 500 мс. Приложение должно продолжать отвечать, только медленнее, а не падать по таймауту.
- Остановить Redis на пару минут. Кеширование должно деградировать до «медленный ответ с базы», а не «пустая страница».
- Отправить в очередь уведомлений «злые» сообщения. Dead-letter queue должен их собрать, воркеры не зависнуть.
- В Kubernetes: удалить под со случайным сервисом. Оркестратор должен восстановить реплику (об этом в финальной статье — контейнеры и оркестрация).
Инструменты: Chaos Mesh и Litmus (Kubernetes), Gremlin (платно), tc/iptables (руками на Linux), Toxiproxy (для сетевых задержек в тестовой среде).
13. Как выбрать, что делать сегодня
| Что у вас есть | Что делать первым | Почему |
|---|---|---|
| Ничего, впервые | benchmark одного ключевого URL | нужна точка отсчёта |
| Есть бенчмарк, хочется «а если» | stress test того же пути | узнать предел |
| Важный релиз впереди | soak за неделю до | выловить утечки |
| Работаете в Kubernetes | chaos test на одном сервисе | проверить устойчивость |
| Есть CI/CD | встроить k6-скрипт в сборку | ловить регресс |
Эти четыре типа — не взаимоисключающие, а дополнительные. Реальная практика: один бенчмарк в неделю, один стресс в месяц, один soak перед релизом, один chaos в квартал.
Часть IV. Реалистичный профиль нагрузки
14. Почему «500 RPS на /» — это не тест
Главная ошибка — тестировать один эндпоинт с одной нагрузкой. Реальный магазин устроен так:
- 60% запросов приходят в кеш (главная, категории, статика) — они вообще не доходят до PHP;
- 25% — карточки товаров, это самый тяжёлый динамический путь;
- 10% — поиск, самый вариабельный по времени;
- 5% — оформляют заказ: POST, блокировки, очередь.
Если вы измерите только /, покажете цифру в 10 раз выше реальной. Если измерите /product/42 — покажете только один срез, без картины в целом.
15. Из чего складывается «правильный» профиль
- Форма волны. Плавный набор (ramp-up), плато (steady state), спуск (ramp-down). Именно так ведёт себя трафик в течение дня, а не 200 VU с первой секунды. В k6 это
stages, в JMeter — «Ultimate Thread Group», в wrk — только руками. - Микс путей. В k6 есть
scenarios— несколько параллельных групп с собственными весами. Задаёте, что 60% делают запрос А, 30% — Б, 10% — В. - Данные. Если все пользователи тестируют
/product/42, они попадают в кеш, и вы меряете кеш, а не приложение. Нужны массивы ID, подставляемые в URL. - Думание. Между действиями пользователя — пауза. Без неё вы гоняете «бот без сна». Пауза с разбросом:
sleep(Math.random() * 4 + 1). - Сессии и cookies. Реальный пользователь логинится один раз и дальше ходит с сессией. k6 с
CookieJarделает это естественным.
16. Как собрать данные из реального трафика
Профиль нагрузки берётся не «с головы», а из логов. Если у вас уже есть продакшн:
- Возьмите логи Nginx за типичный пиковый час (понедельник 12:00–13:00).
- Отрежьте статику и запросы с
4xx— это не ваш профиль. - Посчитайте, сколько раз встречается каждый путь, и подставьте долю в
scenarios. - Соберите частоту статусов и времени ответа — это станет вашим «порогом успеха».
Такой подход даёт «зеркало реальности», а не абстрактный «500 RPS». Если реального трафика ещё нет — смоделируйте профиль по здравому смыслу, но честно скажите себе, что цифра «на глаз», и уточните её через месяц после запуска.
17. Распределение задержек важнее среднего
Средняя задержка почти никогда не описывает реальность. Если среднее — 200 мс, это не значит, что «обычно быстро». Возможно, 95% запросов проходят за 50 мс, а 5% — за 3 секунды. Именно эти 5% и создают жалобы: пользователи помнят худшие случаи, а не среднее.
Поэтому в нагрузочных тестах смотрят на перцентили (percentile). p50 (медиана) — половина запросов быстрее; p95 — 95% быстрее; p99 — почти все. p95 и p99 важнее среднего. Именно их и надо задавать в thresholds.
Часть V. Метрики нагрузки: CPU, память, диск, сеть
18. На что смотреть под нагрузкой
Инструмент нагрузки показывает задержки и ошибки. Это хорошо, но не отвечает на вопрос «почему стало медленно». Ответ даёт мониторинг сервера — тот же, что описан в статье метрики и мониторинг. Вот четыре места, где система упирается.
CPU. У PHP-FPM всё упирается в воркеров: если их меньше, чем ядер, вы используете не всё; если больше, чем памяти, начинается своп. Смотрите по ядрам, не «в среднем по всем». 100% на одном ядре — это и есть упор, даже если средняя загрузка 25%.
Память. Три типичные ситуации:
- PHP worker растёт с каждым запросом (утечка) — надо смотреть на RSS.
- Кеш на диске (
opcache) переполнился и начали выпадать часто используемые скрипты — CPU на парсинге PHP вырастет. - База заняла весь буфер кеша ОС, диск стал читать «как есть».
Диск. Для базы данных это критично: IOPS (operations per second) и latency. 1–5 мс — нормально, 20+ мс — узкое место. Смотрите await в iostat или метрики blkio в cAdvisor.
Сеть. Пропускная способность и число соединений. Если отдаёте большие файлы без CDN — вы ограничены гигабитом. Если не работает keep-alive — вы упираетесь в лимит короткоживущих соединений, а не в трафик. Правильный ответ в обоих случаях часто «вынести статику на CDN и edge-вычисления», а не покупать канал шире.
19. Инструменты быстрой диагностики
| Что случилось | Куда смотреть | Что чинить |
|---|---|---|
| Задержки выросли, CPU 100% | top, mpstat -P ALL 1 | пул PHP-FPM, оптимизация кода |
| Память ползёт вверх на soak | smem, ps -eo rss, график Prometheus | утечка, сборщик мусора, конфиг |
Диск в ожидании (%util) | iostat -x 1 | индексы, кеш базы, SSD вместо HDD |
Сеть в лимите (rxpck/s, txpck/s) | sar -n DEV 1, iftop | CDN, сжатие, HTTP/2, меньший payload |
| Мало RPS, всё в норме | vmstat 1, nstat | узкое место — число потоков/соединений |
Главное правило: ищите не общее среднее, а самое узкое место. Система ограничена самой загруженной подсистемой, и чинить надо её. Повышать CPU, когда всё уперлось в диск, — деньги на ветер.
20. Нагрузочный тест без метрик — это гадание
Очень частый случай: команда запускает k6, смотрит «p95 = 1200 мс, не устраивает» и начинает править код. Через неделю тот же тест даёт 900 мс, команда довольна. На самом деле у них был диск 20 мс по await, после теста кто-то поставил SSD, время ответа упало само. Правки кода оказались бесполезны.
Метрики позволяют связать «стало лучше» с «мы правили то, что реально упиралось». Без этого вы можете улучшать скорость за счёт случайного изменения в инфраструктуре и принимать неверные решения.
Часть VI. Масштабирование под нагрузкой: как Kubernetes сам добавляет копии сервиса
21. Зачем авто-масштабирование
Ручное масштабирование — это «увидели пик, добавили сервер». Он приходит через пять минут, когда пик уже прошёл. Автоматическое масштабирование снимает эту задержку. В Kubernetes это делает HPA (Horizontal Pod Autoscaler, «горизонтальный автоскейлер подов»).
Слово «горизонтальный» означает «больше копий того же сервиса». Противоположность — «вертикальный», когда вы растите одну копию (добавляете CPU/RAM). Горизонтальный путь работает почти для любого stateless-приложения: если ваш PHP-сервис не хранит состояние в памяти (сессии в Redis, а не в PHP), вы спокойно можете поднять десять копий и поставить перед ними балансировщик (см. балансировка нагрузки).
22. Как это работает
HPA опрашивает метрики (стандартно — CPU и память, через Custom Metrics API — произвольные: RPS, p95 задержки, глубина очереди). Каждые несколько секунд он считает «желаемое число реплик» и вызывает масштабирование Deployment. Между шагами — пауза (стабилизационное окно), чтобы не дёргаться при шуме.
Типичная конфигурация:
# hpa-shop.yaml — HPA для Deployment shop-catalog
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: shop-catalog-hpa
namespace: shop
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: shop-catalog # именно этот Deployment будем масштабировать
minReplicas: 2 # никогда не опускаемся ниже двух копий
maxReplicas: 12 # потолок, чтобы не разориться
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization # доля от запрошенного requests.cpu
averageUtilization: 70
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "800"
behavior:
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Percent
value: 100 # удваиваем количество реплик за шаг
periodSeconds: 30
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 1 # снимаем по одной копии раз в 60 секунд
periodSeconds: 60
Что здесь важно понять:
averageUtilization: 70— это не 70% от «всего процессора машины», а 70% отrequests.cpuконкретного пода. Если в Deployment указанrequests.cpu: 500m, то порог срабатывания — когда средний под использует 350m. БезrequestsHPA не сможет считать и вообще не запустится.minReplicas: 2— чтобы не попасть в «скала-к-нулю» и пережить одновременный перезапуск.maxReplicas: 12— предохранитель: если метрика «зависнет» (например, упал источник метрик), система не начнёт плодить реплики бесконечно.behavior.scaleUp/scaleDown— разная скорость вверх и вниз. Вверх — агрессивно (нужно успеть за пиком), вниз — осторожно (не выпилить копии, которые ещё нужны).
23. Как HPA связан с нагрузочным тестом
Это два конца одной задачи:
- Нагрузочный тест показывает, при каком RPS система начинает тормозить.
- HPA держит вас ниже этой точки автоматически.
Если вы протестировали и увидели «12 копий справляются до 10 000 RPS, дальше CPU упирается в 100%» — ставьте maxReplicas: 12 и заранее готовьте что-то ещё. HPA не магия: если вы упёрлись в лимит реплик или в физический лимит нод, он ничего не сделает.
Ещё момент: HPA реагирует с задержкой — интервал опроса плюс окно стабилизации. Резкий всплеск трафика (статья попала в подборку) он закроет с лагом в 30–60 секунд. Если эти секунды критичны, держите запас минимальных реплик (minReplicas) и заранее прогревайте узлы.
24. Когда HPA — это лишнее
Для одного VPS с WordPress и кешем — почти наверняка лишнее. HPA требует Prometheus, Metrics Server, Kubernetes. Для одного сайта это оверкилл: достаточно Nginx с кешем и одного «большого» VPS. HPA начинает окупаться, когда у вас несколько сервисов и вы реально умеете их отдельно масштабировать (см. монолит против микросервисов). Для одиночных PHP-приложений чаще хватает ручного добавления копий + systemd с рестартом.
Часть VII. Автоматизация: проверки производительности в конвейере сборки
25. Что такое CI/CD
CI (Continuous Integration, непрерывная интеграция) — конвейер, который на каждый push в ветку автоматически собирает проект, запускает тесты и даёт обратную связь. CD — либо Continuous Delivery (постоянная доставка: сборка готова, деплой по кнопке), либо Continuous Deployment (постоянное развёртывание: готово — само уехало в прод). Together — CI/CD. Инструменты: GitHub Actions, GitLab CI, Jenkins, TeamCity, Woodpecker.
Нагрузочный тест в CI отвечает на один вопрос: не стал ли код медленнее после этого коммита. Важно: не «достаточно ли быстро приложение», а «не потеряли ли мы скорость относительно вчерашнего замера». Второй вопрос надо задавать в отдельном окружении, на постоянной базе данных, с фиксированной нагрузкой.
26. Схема пайплайна с проверкой скорости
push -> build -> unit tests -> staging deploy -> perf test (k6) -> gate -> production deploy
|
threshold fail -> fail pipeline
Ключевые элементы:
- Отдельное staging-окружение для теста. На проде тестировать нельзя: вы сами себе устроите DDoS.
- Одинаковые данные и конфиг. Если база меняется, цифры плывут. Держите фиксированный дамп.
- Сравнение с baseline. Храните последний «правильный» результат, сравнивайте новый с ним.
- Порог. Если p95 вырос больше, чем на 10%, — красный сигнал.
27. Как это выглядит в GitHub Actions
# .github/workflows/perf.yml — проверка скорости на каждый pull request
name: Perf regression check
on:
pull_request:
paths:
- 'src/**'
- 'composer.json'
- 'Dockerfile'
jobs:
perf:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Запустить приложение в контейнере
run: docker compose -f compose.test.yml up -d
- name: Дождаться готовности
run: curl --retry 30 --retry-delay 2 --fail http://localhost:8080/healthz
- name: Прогнать k6
uses: grafana/k6-action@v0.3.1
with:
filename: perf/load.js
- name: Сохранить артефакт с итогами
run: k6 run perf/load.js --out json=results.json
Тест в CI/CD не заменяет полноценный нагрузочный тест. Он работает как предохранитель: ловит «код, который стал явно хуже». Полноценные stress/soak-прогоны делают на отдельной стенде по расписанию — раз в неделю или перед релизом.
28. Что класть в CI/CD, а что не надо
Подходит для CI:
- короткий benchmark одного-двух путей (30 секунд) с порогом p95;
- регрессия производительности относительно baseline (например, «p95 вырос не более чем на 10%»);
- проверка, что кеш-заголовки не потерялись;
- проверка, что запрос к базе не превысил
EXPLAIN-бюджет по строкам.
Не подходит:
- soak-прогоны — они идут часами;
- стресс-тесты «до отказа» — они нагружают стенд и портят данные;
- chaos — требуют отдельной площадки с осознанным риском.
29. Пороги и baseline: как не обмануть себя
Хранить «одну цифру forever» — плохая практика. Она устаревает, когда вы меняете инфраструктуру. Правильно:
- После каждого успешного релиза обновлять baseline на свежий замер.
- Хранить историю: три-пять последних версий. Если новый замер хуже, чем прошлый baseline, но лучше, чем позапрошлый, — это шум, а не регрессия.
- Писать в отчёт не только «p95 = 1200 мс», а «p95 = 1200 мс, baseline = 1000 мс, +20%» — сразу видно, что произошло.
- Порог задавать не на абсолюте, а на изменении. Например, в
thresholdsk6:p(95)<1200.
Чек-лист: нагрузочное тестирование
- Есть одна точка отсчёта (benchmark) на ключевом пути. Вы знаете, сколько запросов в секунду и с какой p95 вы даёте сегодня.
- Есть профиль нагрузки, а не один URL. Учёт реального микса путей, cookies, пауз между действиями.
- Есть тест-стенд, сопоставимый с продом по мощности. Тестировать на ноутбуке и обещать «выдержим 5000 RPS» — самообман.
- Определены критерии успеха заранее. «p95 < 800 мс, <1% ошибок, без падения при 200 VU». Без этого команда всегда будет спорить.
- Stress-тест найден и описан предел. Вы знаете, где ломаетесь, и что именно ломается.
- Soak пройден на 6+ часов. Память и соединения не ползут.
- Chaos-сценарии задокументированы. Убийство одного бэкенда не валит сайт.
- В CI/CD стоит порог регрессии. Плохой коммит не пройдёт проверку.
- HPA настроен с
requests/limitsиmin/max replicas. Пороги считают отrequests.cpu, иначе они бессмысленны. - Метрики сервера пишутся во время теста. Без них «почему просело» остаётся догадкой.
Итог. Нагрузка как образ мышления
Нагрузочное тестирование — это не «один раз прогнать k6 и записать цифру». Это привычка: понимать, что система имеет пределы, и уметь эти пределы назвать числами до того, как их назовут пользователи. Инструмент выбирается под задачу: wrk — для быстрой прикидки одного эндпоинта, k6 — для сценария с куками и логикой, JMeter — когда нужно много протоколов и GUI. Типы нагрузок дополняют друг друга: benchmark задаёт точку отсчёта, стресс ищет предел, soak ловит утечки, chaos проверяет устойчивость.
Метрики сервера превращают «стало хуже» в «упёрлись в диск». Профиль нагрузки делает цифры честными: вы меряете то, что происходит на проде, а не то, что легко смоделировать. Авто-масштабирование (HPA) превращает тест в практическую возможность: зная потолок, вы настраиваете пороги так, чтобы система не доезжала до него сама. CI/CD-проверка закрепляет это дисциплиной: ни один коммит не уйдёт в прод, если он сделал приложение медленнее на десятки процентов.
Материал написан простым языком. Все примеры рассчитаны на самостоятельный хостинг (VPS) и на WordPress-проекты с собственными PHP-приложениями. Синтаксис k6, JMeter и Kubernetes меняется: пороги
behaviorв HPA пришли с autoscaling/v2, часть параметров wrk отличается между сборками, а в JMeter 5.x «Ultimate Thread Group» — отдельный плагин. Точные версии и документацию стоит сверять на сайтах инструментов.
👤 Зарегистрируйтесь, чтобы добавить материал в закладки.
