В процессе администрирования веб‑сервера наступает момент, когда нужно протестировать производительность решения, оптимизировать настройки, проверить гипотезу. Для решения этих задач нужно выбрать правильный инструмент для бенчмарка, о чём и будем говорить в этой статье. Все примеры будут разобраны на основе сервера Angie, но могут применяться и к другим продуктам. В этой статье будут освещены простые и доступные утилиты для оперативных тестов.
Навигация по циклу
Настройка location в Angie. Разделение динамических и статических запросов.
Перенаправления в Angie: return, rewrite и примеры их применения.
Сжатие текста в Angie: статика, динамика, производительность.
Инструменты для бенчмарка веб-сервера.
Видеоверсия
Для вашего удобства подготовлена видеоверсия этой статьи, доступна на Rutube, VKVideo и YouTube.
Подготовка веб-сервера и окружения
Повторяемость и надёжность результатов бенчмарка зависит от подготовки окружения и самого сервера. Также сразу оговоримся, что нагрузка должна подаваться на сервер под вашим контролем, иначе тестирование может обернуться атакой на отказ в обслуживании.
Применительно к Angie стоит контролировать базовые параметры работы сервера, которые значительно влияют на результаты тестирования (для HTTP):
локальное логирование запросов (
access_log) и ошибок (error_log) создаёт значительную нагрузку на дисковую подсистему;количество рабочих процессов (
worker_processes);настройки динамического сжатия (
gzip, brotli, zstd);набор шифров и тип сертификата (при использовании HTTPS);
используемый протокол (HTTP 1.1, HTTP/2, HTTP/3);
подключённые динамические модули;
настройки
keep-aliveсоединений (в сторону клиента и бэкенда);размер буферов при проксировании (
proxy_buffers);серверное кэширование (
proxy_cache);лимиты на запросы (
limit_reqиlimit_conn);базовая операционная система (используемые библиотеки);
настройки сетевого стека.
Указанные выше настройки и режимы работы должны повторяться и использоваться аналогично при сравнении различных конфигураций, версий или продуктов между собой. Также отсюда следует, что нельзя полагаться на настройки по умолчанию: они могут реализовать разную функциональность и направлены на различные задачи. Например, многие веб‑серверы и СУБД настройками по умолчанию заточены под экономию оперативной памяти и работу на слабом железе, что плохо сочетается с производительностью.
Помимо серверной части также стоит контролировать клиентскую часть (источник нагрузки). Типичные проблемы: достижение предела производительности генератора нагрузки, исчерпание пропускной способности сетевых интерфейсов, фоновая нагрузка на систему. Также можно словить исчерпание эфемерных портов на клиенте или сервере (например, из‑за отключённой опции net.ipv4.tcp_tw_reuse), лимит открытых файловых дескрипторов и другие логические ограничения. Причём, первый прогон теста может уложиться в лимиты, а следующий будет сыпать ошибками из‑за сокетов в состоянии TIME‑WAIT. В результате бенчмарки показывают разгромные результаты в пользу одного из участников просто из‑за ошибок подготовки окружения.
Таким образом, любой бенчмарк это не только и не столько запуск тестов, а процесс анализа результатов и понимания тонкостей работы продукта и его взаимодействия со средой (операционной системой, железом, окружением). Именно поэтому процесс тестирования производительности крайне полезный и интересный вид деятельности.
Типы бенчмарков: closed-loop и open-loop
Перед обзором утилит для тестирования производительности веб‑серверов введём классификацию подобных тестов по обработке ответов:
закрытого цикла (closed-loop);
открытого цикла (open-loop).
Утилиты, работающие по закрытому циклу (closed‑loop) отправляют запросы c той скоростью, с которой получают ответы от сервера. Таким образом, когда сервер начинает отвечать медленее под нагрузкой, частота запросов снижается. По сути, закрытый цикл сильно зависит от времени ответа сервера (latency), но не всегда даёт полные данные о пропускной способности (throughput) сервера. Примеры утилит такого типа: ab, wrk, wrk2, h2load.
Средства тестирования с открытым циклом (open‑loop) создают нагрузку в соответствии с настройкой частоты запросов, при этом не ждут ответа и не зависят от времени ответа сервера. Если нагрузка будет превышать возможности сервера, стоит ожидать ошибок в ответах (таймауты, ошибки подключения и так далее) Такой режим позволяет более реалистично эмулировать нагрузку реальных пользователей. Примеры утилит с открытым циклом: Яндекс.Танк, httperf, Vegeta. Некоторые средства имеют несколько режимов работы, например: Grafana k6, Nighthawk.
Утилита ab (Apache Benchmark)
Apache Benchmark (ab) это простая утилита с неплохими возможностями. Утилита поставляется в пакете apache2-utils (для Ubuntu).
Базовый тест можно запустить одной командой:
ab -c 10 -l -k -n 5000 http://localhost/
Разберём параметры этой команды:
‑c 10— уровень конкурентности запросов (эмуляция нескольких клиентов);‑l— допустить различную длину ответов (иначе отличия будут считаться ошибками);‑k— использовать keep‑alive соединения (по умолчанию отключены);‑n 5000— количество запросов в тесте (50000);http://localhost/— URL запроса.
Так как ab это однопоточная утилита, то происходит именно эмуляция параллельных клиентов, а не реальный запуск нескольких потоков. Следовательно, утилита будет ограничена производительностью одного ядра процессора. В случае тестирования Angie в такой предел можно упереться даже при одном рабочем процессе на сервере.
Результат теста представлен в следующем виде:
Benchmarking localhost (be patient) Completed 500 requests ... Completed 5000 requests Finished 5000 requests Server Software: Angie/1.11.4 Server Hostname: localhost Server Port: 80 Document Path: / Document Length: Variable Concurrency Level: 10 Time taken for tests: 8.129 seconds Complete requests: 5000 Failed requests: 0 Keep-Alive requests: 5000 Total transferred: 54581671 bytes HTML transferred: 53641671 bytes Requests per second: 615.08 [#/sec] (mean) Time per request: 16.258 [ms] (mean) Time per request: 1.626 [ms] (mean, across all concurrent requests) Transfer rate: 6557.09 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.0 0 1 Processing: 2 16 29.4 13 205 Waiting: 2 16 29.4 13 205 Total: 2 16 29.4 13 205 Percentage of the requests served within a certain time (ms) 50% 13 66% 14 75% 14 80% 15 90% 17 95% 36 98% 158 99% 167 100% 205 (longest request)
Первая часть отчета показывает настройки теста и суммарные значения. Основной интерес представляет значение запросов в секунду (Requests per second) и время ответа (Time per request). Также важно сверять использованную пропускную способность (Transfer rate) с возможностями сетевого интерфейса. Если значение этого параметра приближается к максимальному для интерфейса, значит тест некорректен.
Также очень важно распределение времени ответа (перцентили). Эти значения показывают время ответа, в которое уложились x% ответов. Например, в результатах выше 95-й перцентиль равен 36 мс, то есть 95% ответов были получены за 36 мс или меньше. Соответственно, 100-й перцентиль означает время самого медленного ответа, 50-й это медиана.
При наличии ошибок утилита указывает на ответы с кодом, отличным от 200 (non-200 replies). Для игнорирования ошибок сокета можно добавить ключ ‑r, иначе тест заканчивается при первой такой ошибке.
Утилита ab поддерживает протокол HTTP/1.1 и TLS 1.2. Можно использовать различные HTTP‑методы (параметр ‑m), HTTP‑аутентификацию (параметр ‑A), а также mTLS (аутентификацию с помощью клиентского сертификата — параметр ‑E).
Часто при тестировании полезно задавать HTTP‑заголовки запроса (‑H), например, можно анонсировать поддержку сжатия:
ab -n 200 -c 4 -H "Accept-Encoding: gzip,deflate,br" https://localhost/
Подводя итог по утилите ab можно сказать, что она полезна для тестирования с использованием протокола HTTP/1.1 и TLS 1.2. При этом возможность создания нагрузки ограничена одним потоком исполнения, чего недостаточно для тестов быстрых сценариев использования Angie с использованием нескольких рабочих процессов. Частично это ограничение можно обойти за счет параллельного запуска нескольких экземпляров ab. Например, так:
apt install parallel parallel --ungroup -j 2 'ab -n 1000 -c 10 {}' ::: 'http://localhost/' 'http://localhost/'
Однако, результаты теста придётся дополнительно обрабатывать. На этом перейдём к более продвинутому инструменту: wrk.
Утилиты wrk/wrkx
Проект wrk имеет богатую историю. Это многопоточная утилита для нагрузочного тестирования и бенчмарков веб‑серверов и приложений. На текущий момент проект не развивается более 5 лет, но есть активные форки с важными доработками относительно оригинальной версии. Версия wrk2 добавляет возможность контроля нагрузки (параметр ‑R, определяет RPS) и более точно измеряет время ответа.
Вариант, который мы будем использовать далее это wrkx, форк wrk2 от Алексея Рыбака. В wrkx внедрены несколько патчей по повышению стабильности работы и возможности привязки потоков к процессорам (CPU affinity).
Для использования wrkx нужно собрать из исходников, благо это несложно. Пример процесса для Ubuntu 24.04:
apt install git build-essential libssl-dev zlib1g-dev git clone https://github.com/devhands-io/wrkx.git cd wrkx make
Если всё прошло успешно, получим бинарный исполняемый файл wrk в директории клонированного проекта. При запуске важно не перепутать обычный wrk установленный в системе (из одноимённого пакета) и этот собранный файл.
Запустить простой тест с wrkx можно так (обратите внимание на указание пути к файлу):
./wrk -t8 -c48 -d20s -R2000 --timeout 5s http://localhost/
Разберём параметры этой команды:
‑t8— запустить 8 потоков для создания нагрузки;‑с48— количество параллельных подключений на все потоки;‑d20s— длительность теста;‑R2000— создать суммарную нагрузку в 2000 RPS (запросов в секунду);‑timeout 5s— время ожидания ответа от сервераhttp://localhost/— URL для запроса.
Таким образом, для теста определяется длительность и ограничение по частоте запросов. По умолчанию wrk создаёт keep-alive подключения. Благодаря многопоточности, wrk может создавать высокий уровень нагрузки. Как и ab, wrk может задавать заголовки запроса:
./wrk -t2 -c48 -d30s -R2000 -H "Accept-Encoding: gzip" --timeout 5s http://localhost
Теперь результаты включают детальные параметры распределения времени ответа (часть вывода опущена):
Running 30s test @ http://localhost 2 threads and 48 connections Thread calibration: mean lat.: 3844.563ms, rate sampling interval: 13623ms Thread calibration: mean lat.: 3788.619ms, rate sampling interval: 13557ms Thread Stats Avg Stdev Max +/- Stdev Latency 14.90s 3.98s 22.95s 56.98% Req/Sec 244.50 0.50 245.00 100.00% Latency Distribution (HdrHistogram - Recorded Latency) 50.000% 15.68s 75.000% 17.97s 90.000% 19.97s 99.000% 22.50s 99.900% 22.77s 99.990% 22.92s 99.999% 22.97s 100.000% 22.97s Detailed Percentile spectrum: Value Percentile TotalCount 1/(1-Percentile) 7155.711 0.000000 1 1.00 9789.439 0.100000 951 1.1 ... 22970.367 0.999902 9421 10240.00 22970.367 1.000000 9421 inf #[Mean = 14897.478, StdDeviation = 3975.822] #[Max = 22953.984, Total count = 9421] #[Buckets = 27, SubBuckets = 2048] ---------------------------------------------------------- 14874 requests in 30.04s, 150.82MB read Requests/sec: 495.20 Transfer/sec: 5.02MB
Детальные данные о времени ответа можно использовать для визуализации HDR‑гистограммы.
Утилита wrk работает с протоколом HTTP/1.1, возможности TLS зависят от используемой при сборке версии библиотеки. Помимо простых тестов, wrk поддерживает скрипты на языке Lua для более сложных сценариев.
А что, если потребуется протестировать работу сервера с протоколом HTTP/2? Можно воспользоваться инструментом h2load.
Утилита h2load
Все разобранные выше инструменты могли работать только с HTTP/1.1. Однако, на сегодняшний день самым популярным протоколом для фронт-серверов является HTTP/2. Создать тест с применением HTTP/2 можно утилитой h2load из проекта nghttp2. Для Ubuntu 24.04 доступен пакет:
apt install nghttp2-client
Запуск теста аналогичен другим утилитам:
h2load -n 500000 -c 100 -t 4 https://localhost/
По умолчанию h2load использует протокол HTTP/2. Разберём параметры этого теста подробнее:
-n 500000— количество запросов;-c 100— количество параллельных подключений;-t 4— количество потоков;https://localhost/— URI запроса.
Для использования протокола HTTP/1.1 можно указать параметр ‑h1. В экспериментальном режиме утилита поддерживает работу с протоколом HTTP/3, поддержка активируется ключом ‑h3.
Посмотрим на результаты теста:
starting benchmark... spawning thread #0: 5 total client(s). 2500 total requests spawning thread #1: 5 total client(s). 2500 total requests TLS Protocol: TLSv1.3 Cipher: TLS_AES_128_GCM_SHA256 Server Temp Key: X25519 253 bits Application protocol: h2 progress: 10% done ... progress: 100% done finished in 8.88s, 563.37 req/s, 5.75MB/s requests: 5000 total, 5000 started, 5000 done, 4997 succeeded, 3 failed, 0 errored, 0 timeout status codes: 4997 2xx, 0 3xx, 0 4xx, 3 5xx traffic: 51.07MB (53546209) total, 371.03KB (379934) headers (space savings 43.70%), 50.53MB (52985839) data min max mean sd +/- sd time for request: 895us 1.13s 17.13ms 58.48ms 95.32% time for connect: 2.23ms 6.40ms 4.30ms 1.46ms 60.00% time to 1st byte: 7.09ms 27.95ms 13.57ms 7.88ms 80.00% req/s : 56.34 63.28 58.42 2.56 80.00%
В результатах можно найти общую статистику по кодам ответа сервера (status codes) и полученные значения RPS (req/s), пропускной способности (MB/s), а также статистику времени ответа (финальная таблица). Из дополнительных данных можно увидеть эффективность сжатия заголовков (space savings) в строке (traffic).
Утилита полезна прежде всего возможностью сравнения работы протоколов HTTP/1.1 и HTTP/2, при этом обладает высокой эффективностью, сравнимой с wrkx.
Итоги
Каждая из рассмотренных утилит обладает своими достоинствами и недостатками, при этом хорошо решает свои задачи. Как наиболее универсальный инструмент можно выделить wrkx. Для работы с новыми протоколами можно рекомендовать h2load. За пределами статьи остались более сложные системы тестирования, например k6 от Grafana, Apache JMeter или Яндекс.Танк. Они могут реализовать более сложные сценарии тестирования и наделены широкими возможностями.