В первой главе мы научились пользоваться curl как стетоскопом: один запрос — и сразу видно DNS, подключение, TLS, TTFB и общее время ответа.
Но в реальной жизни одного замера почти никогда недостаточно. Сегодня сайт может «стрельнуть» быстро один раз и провалиться на следующем. Нагрузка скачет, кэш прогревается, база иногда думает дольше. Поэтому во второй главе мы научимся:
прогонять много запросов подряд и считать среднее/проценты;
сравнивать IPv4 и IPv6;
стрелять в конкретный бэкенд за балансировщиком;
смотреть, как ведут себя редиректы и разные версии HTTP;
собирать из этого уже похожий на правду мини‑бенчмарк.
При этом я буду держать курс на то, чтобы даже начинающему админу было понятно, что и зачем делаем.
Мини‑бенчмарк: многократные замеры и p95
Один запуск curl — это фотография. Нам нужна маленькая «видеозапись» поведения сайта.
Сделаем простой bash‑скрипт, который:
много раз дергает URL;
печатает только
time_total(полное время);в конце считает среднее и p95 (95‑й процент).
Создадим файл, например curl-bench.sh:
#!/usr/bin/env bash
URL="$1"
COUNT="${2:-20}" # default 20 requests
if [[ -z "$URL" ]]; then
echo "Usage: $0 <url> [count]"
exit 1
fi
# collect timings into an array
results=()
for i in $(seq 1 "$COUNT"); do
t=$(curl -s -o /dev/null -w '%{time_total}' "$URL")
results+=("$t")
echo "#${i}: ${t}s"
done
# sorting and calculating statistics
sorted=$(printf '%s
' "${results[@]}" | sort -n)
sum=0
n=0
while read -r v; do
sum=$(awk -v s="$sum" -v v="$v" 'BEGIN {printf "%.6f", s+v}')
n=$((n+1))
vals+=("$v")
done <<< "$sorted"
avg=$(awk -v s="$sum" -v n="$n" 'BEGIN {printf "%.4f", s/n}')
# p95 — element with index floor(0.95*n)
idx=$(awk -v n="$n" 'BEGIN {printf "%d", (n*0.95)}')
(( idx < 1 )) && idx=1
p95=${vals[$((idx-1))]}
echo "-------------------------"
echo "requests: $n"
echo "avg: ${avg}s"
echo "p95: ${p95}s"
Делаем исполняемым:
chmod +x curl-bench.sh
И запускаем:
./curl-bench.sh https://example.com/ 30
На выходе вы получите 30 строк вида #N: 0.423s и внизу краткую сводку:
-------------------------
requests: 30
avg: 0.4100s
p95: 0.6500s
Как это интерпретировать
avg (среднее) — средняя температура по больнице. Хорошо показывает общее состояние, но его может испортить один очень медленный запрос.
p95 — 95‑й процент. 95% запросов быстрее этого значения. Именно p95 ближе всего к тому, «как сайт ощущается» пользователям.
Если avg 0.3s, а p95 1.2s — иногда у вас происходят всплески задержки (скорее всего, база/очереди/соседняя нагрузка).
IPv4 vs IPv6: кто быстрее
Многие сайты уже отдают контент как по IPv4, так и по IPv6. Иногда один стек работает заметно медленнее другого.
Сравним:
# IPv4
curl -4 -s -o /dev/null -w @curl-format.txt https://example.com/
# IPv6
curl -6 -s -o /dev/null -w @curl-format.txt https://example.com/
На что смотрим:
если
time_connectпо IPv6 существенно больше — возможно, проблемы маршрутизации или фильтры по пути;если
time_namelookupсильно отличается — проверьте AAAA‑записи и настройки DNS;если
time_totalрастёт только по IPv6, а TTFB примерно одинаковый — подозрение на сеть (MTU, потери, асимметричный маршрут).
При необходимости можно прогнать наш curl-bench.sh отдельно с ключами -4 и -6, слегка доработав скрипт (добавить параметр для передачи этих опций).
Стреляем по конкретному бэкенду за балансировщиком
В продакшене у вас чаще всего не один сервер, а несколько бэкендов за балансировщиком. Иногда «тормозит не сайт, а конкретная нода».
--resolve: фиксируем IP для домена
Если вы знаете IP конкретного сервера, можно обойти DNS и балансировщик:
curl -s -o /dev/null -w @curl-format.txt \
--resolve site.com:443:203.0.113.10 \
https://site.com/
Что делает --resolve:
говорит
curl: «дляsite.com:443используй IP203.0.113.10, не трогай DNS»;при этом в HTTP‑запросе остаётся Host:
site.com, то есть виртуальный хост на бэкенде работает как обычно.
Так можно сравнить тайминги разных бэкендов:
curl -s -o /dev/null -w @curl-format.txt \
--resolve site.com:443:203.0.113.10 https://site.com/
curl -s -o /dev/null -w @curl-format.txt \
--resolve site.com:443:203.0.113.11 https://site.com/
Если один сервер стабильно медленнее другого по TTFB — есть повод искать проблему именно на нём (нагрузка, медленный диск, локальные логи, кривые кэш‑правила и т.п.).
--connect-to: перекидываем трафик на другой хост/порт
Иногда нужен более гибкий вариант: изменить и хост, и порт назначения. Для этого есть --connect-to:
curl -s -o /dev/null -w @curl-format.txt \
--connect-to site.com:443:10.0.0.5:8443 \
https://site.com/
Смысл такой:
логически мы идём на
https://site.com:443;физически
curlсоединяется с10.0.0.5:8443;в заголовке Host остаётся
site.com.
Этим удобно ходить на тестовые бэкенды, нестандартные порты, локальные dev‑окружения, не меняя URL и не трогая DNS.
HTTP/1.1, HTTP/2, HTTP/3: влияние протокола
Если ваш curl собран с поддержкой HTTP/2 и HTTP/3, можно посмотреть, как меняется время ответа при разных версиях протокола.
Примеры:
# Force HTTP/1.1
curl --http1.1 -s -o /dev/null -w @curl-format.txt https://site.com/
# HTTP/2
curl --http2 -s -o /dev/null -w @curl-format.txt https://site.com/
# HTTP/3 (if the server and curl support)
curl --http3 -s -o /dev/null -w @curl-format.txt https://site.com/
Что здесь важно понимать начинающему админу:
для одной простой страницы без кучи ресурсов разница может быть почти незаметна;
преимущество HTTP/2/3 особенно видно, когда много параллельных запросов (картинки, CSS, JS);
иногда HTTPS + HTTP/2 на старом железе или под специфичным прокси может, наоборот, ухудшать ситуацию.
Задача curl в этом контексте — дать вам «голое» время ответа по каждому протоколу.
Редиректы: декомпозируем цепочку
В первой главе мы включали опцию -L, чтобы curl следовал редиректам. Но как посмотреть, где именно в цепочке мы теряем время? Одна из простых тактик — пройти цепочку по шагам вручную.
Сначала смотрим только заголовки (-I)
curl -I https://site.com/
Вы увидите что‑то вроде:
HTTP/2 301
location: https://www.site.com/
...
Дальше можно пройтись по каждому шагу отдельно:
curl -s -o /dev/null -w @curl-format.txt https://site.com/
curl -s -o /dev/null -w @curl-format.txt https://www.site.com/
Если первый шаг быстрый, а второй тормозит — проблема явно не в редиректе, а уже в конечной точке.
Автоматическое следование (-L) с отображением кода ответа
Можно добавить в формат вывода переменные %{url_effective} и %{http_code}, чтобы после всех редиректов увидеть итоговый URL и статус:
curl -s -L -o /dev/null -w 'code: %{http_code}\nurl: %{url_effective}\n' https://site.com/
Это не даст тайминги по каждому отдельному шагу, но покажет итоговую комбинацию «куда пришли» + «чем закончилось».
В более сложных сценариях (много редиректов, разные домены) обычно всё же удобнее пройтись вручную по каждому звену.
Измеряем разные этапы отдельно: TTFB vs размер ответа
Иногда важно разделить:
время, пока сервер думает;
время, пока контент докачивается.
Напомним ключевые моменты:
time_starttransfer— примерно TTFB, время до первого байта;time_total— полное время до получения всего тела.
Если вы хотите прямо видеть разницу, добавьте в формат строку вроде:
Server processing (TTFB): %{time_starttransfer}s
Download tail: %{time_total} - %{time_starttransfer}s
К сожалению, curl не умеет вычитать переменные прямо в формате, но вы можете посчитать это в своей голове или в небольшом скрипте, где вызываете curl и затем через awk считаете разность.
Пример грубого скрипта:
curl -s -o /dev/null -w '%{time_starttransfer} %{time_total}\n' https://site.com/ \
| awk '{ttfb=$1; total=$2; tail=total-ttfb; \
printf "TTFB: %.3fs\nTail: %.3fs\n", ttfb, tail}'
Если TTFB большой — смотрим в сторону бэкенда и базы. Если Tail странно большой — возможно, ответ очень тяжёлый или где‑то по пути проблемы с пропускной способностью.
Дополнительные заголовки и дебаг: что сервер ответил на самом деле
Иногда число секунд — это ещё не всё. Хотите понять, попали ли вы в кэш, в какой датацентр, на какой ноде оказались? Смотрим заголовки.
Пример:
curl -s -D - -o /dev/null https://site.com/
Опция -D - выводит заголовки ответа. Часто там есть служебные поля:
X-Cache: HIT/MISS— попали ли мы в кэш;X-Served-By,X-Backend— имя бэкенда или датацентра;Server-Timing— иногда там лежат внутренние тайминги приложения.
Сочетая это с нашими числовыми таймингами, можно довольно быстро понять, где «узкое место».
Мини‑итог
Во второй главе мы:
превратили
curlиз разового выстрела в мини‑бенчмарк с несколькими запросами, средним и p95;научились сравнивать IPv4 и IPv6;
научились стрелять в конкретный бэкенд за балансировщиком с помощью
--resolveи--connect-to;посмотрели, как влияет версия протокола (HTTP/1.1, HTTP/2, HTTP/3);
научились разбирать цепочки редиректов и отделять TTFB от времени докачки тела ответа;
увидели, как вытягивать заголовки для дополнительного контекста (кэш, датацентр, нода).
На практике всё это позволяет вам не просто говорить «сайт медленный», а чётко формулировать: «по IPv6 из Нидерландов p95 вырос до 900 мс, при этом TTFB нормальный, а хвост докачки большой — подозреваю ограничение полосы на конкретном узле или по пути». А пока можно уже использовать скрипт curl-bench.sh в повседневной жизни, чтобы перестать спорить «на глаз» и начать оперировать цифрами.