↩️ Назад

Категории

Диагностика времени ответа сайта: от shared-хостинга до своего скрипта-монитора

27.05.2026 | из категории: Создание сайтов

Диагностика работы сайта - хостинг и vps

Вместо предисловия: была проблема

Сайт вроде работает, но иногда — непонятно. Zabbix рисует красивые графики, где время ответа скачет до 5 секунд. Пинг в норме, DNS в норме. Что за чёрт?

Мы перебрали всё: ковыряли Zabbix, смотрели DNS, меняли хостинг (переехали на VPS), добавили для сравнения mail.ru и сайт на Тильде. И только когда написали свой маленький скрипт-монитор, картинка прояснилась. (пока еще не прояснилась, с утра проанализирую логи за сутки)

В этой статье расскажу:

Спойлер: проблема почти всегда не в вашем коде, а в очереди. Очереди PHP-воркеров на shared-хостинге или очереди дискового ввода-вывода на дешёвом VDS.


Часть 1. Что такое «гребёнка» и откуда она берётся

Когда вы смотрите на график времени ответа (TTFB, Time To First Byte), идеальная картинка — ровная линия на уровне 0.1–0.3 сек. А если график похож на пилу или гребёнку — это проблема.

1. Shared-хостинг (500 сайтов на одном сервере)

Главный враг — очередь PHP-воркеров. Выделено, условно, 50 воркеров на всех. В час пик соседний Битрикс или магазин на OpenCart застревают на 5 секунд, занимают воркеры — и ваш запрос ждёт в очереди. При этом сам PHP-скрипт у вас отрабатывает за 0.2 сек, но пользователь видит 5 секунд ожидания.

Диагноз: TTFB скачет, время выполнения скрипта — в норме. Лечится переездом на VPS.

2. VPS на HDD

У вас свои воркеры, но диск — механический. Когда соседние VPS на том же физическом сервере начинают активно читать/писать (бэкапы, кроны, парсинг), возникает I/O Wait. Ваш PHP пытается прочитать файл или обратиться к БД — диск занят. Снова очередь, снова секундные пики.

Диагноз: TTFB скачет вместе с I/O Wait. Лечится переездом на VPS с NVMe.

3. Почему mail.ru работает идеально

Не потому, что сервер «настроили», а потому что у них:

Но нам до них далеко. Нам нужен простой и честный способ замерить TTFB без Zabbix, без лишних слоёв.


Часть 2. Пишем скрипт для замера TTFB

Задача простая: каждые N секунд ходим на сайт, замеряем time_starttransfer (это и есть TTFB), пишем в CSV. Потом анализируем.

2.1. Основной скрипт (site_monitor.sh)

#!/bin/bash

# Список сайтов для мониторинга
SITES=(
    "logosad.ru"
    "franchisedar.ru"
    "doctorneiro.ru"
    "mail.ru"
    "logoakademia.ru"
)

# Директория для логов
LOG_DIR="$HOME/site_monitor"
mkdir -p "$LOG_DIR"

# Файл лога с датой (ротация по дням)
LOG_FILE="$LOG_DIR/response_$(date +%Y%m%d).csv"

# Добавляем заголовок, если файл новый
if [ ! -f "$LOG_FILE" ]; then
    echo "timestamp;site;ttfb_seconds;http_code" > "$LOG_FILE"
fi

echo "=== $(date +%H:%M:%S) ==="

for site in "${SITES[@]}"; do
    timestamp=$(date +"%Y-%m-%d %H:%M:%S")
    
    # Замеряем TTFB и HTTP-код
    result=$(curl -s -o /dev/null -w "%{time_starttransfer};%{http_code}" \
        --max-time 10 \
        --connect-timeout 3 \
        "http://$site" 2>&1)
    
    ttfb=$(echo "$result" | cut -d';' -f1)
    http_code=$(echo "$result" | cut -d';' -f2)
    
    # Пишем в CSV
    echo "\"$timestamp\";\"$site\";\"$ttfb\";\"$http_code\"" >> "$LOG_FILE"
    
    # Печатаем в консоль (для отладки)
    echo "  $site → ${ttfb}s (HTTP $http_code)"
    
    sleep 1
done

echo "---"

2.2. Цикл для запуска каждые 10 секунд (loop_monitor.sh)

#!/bin/bash
while true; do
    $HOME/site_monitor/site_monitor.sh
    sleep 10
done

2.3. Запуск через screen (чтобы работало после выхода из SSH)

# Даём права на выполнение
chmod +x $HOME/site_monitor/site_monitor.sh
chmod +x $HOME/site_monitor/loop_monitor.sh

# Запускаем в фоновой screen-сессии
screen -dmS monitor $HOME/site_monitor/loop_monitor.sh

# Проверить, что запустилось
screen -ls

# Подключиться и посмотреть живой вывод
screen -r monitor

# Выйти из screen, оставив процесс работать: Ctrl+A, затем D

Часть 3. Анализ собранных данных

Через несколько часов (или утром) у нас будет CSV-файл с колонками:

Вот скрипт анализа analyze.sh, который покажет:

#!/bin/bash

LOG_FILE="/home/user/site_monitor/response_$(date +%Y%m%d).csv"

if [ ! -f "$LOG_FILE" ]; then
    echo "Лог файл не найден: $LOG_FILE"
    exit 1
fi

echo "========================================="
echo "Анализ TTFB за $(date +%Y-%m-%d)"
echo "Всего строк в логе: $(wc -l < $LOG_FILE)"
echo "========================================="

for site in logosad.ru franchisedar.ru doctorneiro.ru mail.ru logoakademia.ru; do
    echo ""
    echo "📊 $site"
    
    # Собираем только корректные числа
    data=$(grep "\"$site\"" "$LOG_FILE" 2>/dev/null | awk -F';' '{print $3}' | sed 's/"//g' | grep -E '^[0-9]+\.[0-9]+$' | awk '$1 > 0.001')
    
    if [ -n "$data" ] && [ "$(echo "$data" | wc -l)" -gt 0 ]; then
        count=$(echo "$data" | wc -l)
        min=$(echo "$data" | sort -n | head -1)
        max=$(echo "$data" | sort -n | tail -1)
        avg=$(echo "$data" | awk '{sum+=$1} END {printf "%.4f", sum/NR}')
        median=$(echo "$data" | sort -n | awk '{a[NR]=$1} END {printf "%.4f", a[int(NR/2)]}')
        p95=$(echo "$data" | sort -n | awk '{a[NR]=$1} END {printf "%.4f", a[int(NR*0.95)]}')
        
        # Считаем медленные (>500 мс) и очень медленные (>1000 мс)
        slow_500=$(echo "$data" | awk '$1 > 0.5 {count++} END {print count+0}')
        slow_1000=$(echo "$data" | awk '$1 > 1.0 {count++} END {print count+0}')
        
        # Проценты
        slow_500_pct=$(awk "BEGIN {printf \"%.2f\", $slow_500 * 100 / $count}")
        slow_1000_pct=$(awk "BEGIN {printf \"%.2f\", $slow_1000 * 100 / $count}")
        
        echo "   ═══════════════════════════════════════"
        echo "   📈 Статистика по сайту:"
        echo "   ═══════════════════════════════════════"
        echo "   Всего запросов:     $count"
        echo "   Средний TTFB:       ${avg} сек"
        echo "   Медиана:            ${median} сек"
        echo "   95-й перцентиль:    ${p95} сек"
        echo "   Мин / Макс:         ${min} / ${max} сек"
        echo ""
        echo "   ═══════════════════════════════════════"
        echo "   🐌 Медленные ответы (>500 мс):"
        echo "   ═══════════════════════════════════════"
        echo "   $slow_500 запросов (${slow_500_pct}%) из $count"
        
        if (( $(echo "$slow_500_pct > 1.0" | bc 2>/dev/null || echo "0") )); then
            echo "   ⚠️  ВНИМАНИЕ: больше 1% ответов медленнее 500 мс!"
        elif (( $(echo "$slow_500_pct > 0.5" | bc 2>/dev/null || echo "0") )); then
            echo "   ⚠️  Есть задержки: ${slow_500_pct}% ответов >500 мс"
        elif [ "$slow_500" -gt 0 ]; then
            echo "   ✅ Допустимо: ${slow_500_pct}% ответов >500 мс"
        else
            echo "   ✅ Идеально: ни одного ответа медленнее 500 мс"
        fi
        
        echo ""
        echo "   ═══════════════════════════════════════"
        echo "   🔥 Пиковые ответы (>1000 мс / 1 сек):"
        echo "   ═══════════════════════════════════════"
        echo "   $slow_1000 запросов (${slow_1000_pct}%) из $count"
        
        if [ "$slow_1000" -gt 0 ]; then
            echo "   🚨 Есть пики: ${slow_1000_pct}% запросов >1 секунды"
        else
            echo "   ✅ Прекрасно: ни одного пика >1 секунды"
        fi
        
    else
        echo "   ❌ Нет корректных данных"
    fi
done

echo ""
echo "========================================="
echo "📊 СВОДНАЯ ТАБЛИЦА ПО ВСЕМ САЙТАМ"
echo "========================================="
echo ""

printf "%-20s | %10s | %12s | %12s\n" "Сайт" "Запросов" ">500 мс" ">1000 мс"
printf "%-20s-+-%10s-+-%12s-+-%12s\n" "--------------------" "----------" "------------" "------------"

for site in logosad.ru franchisedar.ru doctorneiro.ru mail.ru logoakademia.ru; do
    data=$(grep "\"$site\"" "$LOG_FILE" 2>/dev/null | awk -F';' '{print $3}' | sed 's/"//g' | grep -E '^[0-9]+\.[0-9]+$' | awk '$1 > 0.001')
    
    if [ -n "$data" ] && [ "$(echo "$data" | wc -l)" -gt 0 ]; then
        count=$(echo "$data" | wc -l)
        slow_500=$(echo "$data" | awk '$1 > 0.5 {count++} END {print count+0}')
        slow_1000=$(echo "$data" | awk '$1 > 1.0 {count++} END {print count+0}')
        
        slow_500_pct=$(awk "BEGIN {printf \"%.2f\", $slow_500 * 100 / $count}")
        slow_1000_pct=$(awk "BEGIN {printf \"%.2f\", $slow_1000 * 100 / $count}")
        
        printf "%-20s | %10d | %5d (%5s%%) | %5d (%5s%%)\n" "$site" "$count" "$slow_500" "$slow_500_pct" "$slow_1000" "$slow_1000_pct"
    else
        printf "%-20s | %10s | %12s | %12s\n" "$site" "нет данных" "-" "-"
    fi
done

echo ""
echo "========================================="
echo "🏆 РЕЙТИНГ ПО СТАБИЛЬНОСТИ (меньше % >500мс = лучше)"
echo "========================================="
echo ""

# Создаём временный файл для рейтинга
rank_file="/tmp/ttfb_rank.txt"
> "$rank_file"

for site in logosad.ru franchisedar.ru doctorneiro.ru mail.ru logoakademia.ru; do
    data=$(grep "\"$site\"" "$LOG_FILE" 2>/dev/null | awk -F';' '{print $3}' | sed 's/"//g' | grep -E '^[0-9]+\.[0-9]+$' | awk '$1 > 0.001')
    
    if [ -n "$data" ] && [ "$(echo "$data" | wc -l)" -gt 0 ]; then
        count=$(echo "$data" | wc -l)
        slow_500=$(echo "$data" | awk '$1 > 0.5 {count++} END {print count+0}')
        slow_500_pct=$(awk "BEGIN {printf \"%.2f\", $slow_500 * 100 / $count}")
        echo "$slow_500_pct $site" >> "$rank_file"
    fi
done

# Сортируем и выводим
i=1
while read -r line; do
    pct=$(echo "$line" | awk '{print $1}')
    name=$(echo "$line" | awk '{print $2}')
    if [ "$pct" = "0.00" ]; then
        echo "   🥇 $i место: $name — 0% (абсолютная стабильность)"
    elif (( $(echo "$pct < 0.5" | bc -l 2>/dev/null || echo "0") )); then
        echo "   🥈 $i место: $name — ${pct}% (отличная стабильность)"
    elif (( $(echo "$pct < 1.0" | bc -l 2>/dev/null || echo "0") )); then
        echo "   🥉 $i место: $name — ${pct}% (хорошая стабильность)"
    else
        echo "   $i место: $name — ${pct}% (есть задержки)"
    fi
    i=$((i+1))
done < <(sort -n "$rank_file")

rm -f "$rank_file"

echo ""
echo "========================================="
echo "💡 ПОЯСНЕНИЕ"
echo "========================================="
echo "   • >500 мс  — ответ медленнее 0.5 секунды (пользователь замечает)"
echo "   • >1000 мс — ответ медленнее 1 секунды (уже неприятно)"
echo "   • 0.4-0.5% — допустимо для обычного VPS"
echo "   • 0.1-0.3% — отлично"
echo "   • 0%       — уровень mail.ru / Google"

Запуск анализа

chmod +x $HOME/site_monitor/analyze.sh
$HOME/site_monitor/analyze.sh

Часть 4. Что мы узнали на практике

Результаты первого замера (все сайты показали отлично)

=== 16:02:48 ===
  logosad.ru → 0.037s (HTTP 301)
  franchisedar.ru → 0.044s (HTTP 301)
  doctorneiro.ru → 0.037s (HTTP 301)
  mail.ru → 0.028s (HTTP 301)
  logoakademia.ru → 0.028s (HTTP 301)
---

Вывод: в данный момент все серверы работают быстро. Но главный вопрос — появятся ли пики ночью или в час пик? Для этого мы и запускаем мониторинг на сутки.

Ожидаемые сценарии


Заключение. Что делать дальше

Наш маленький скрипт даёт честную картину времени ответа, без прослоек вроде Zabbix, который иногда сам создаёт нагрузку. Он показал, что:

Рекомендация: возьмите скрипт, запустите на своём сервере на сутки, получите цифры и принимайте решение — менять хостинг, апгрейдить диск или оптимизировать код.

А если в процессе найдёте что-то интересное — поделитесь в комментариях.


P.S. Если лень заморачиваться со screen, можно запустить через cron раз в минуту. Но 10 секунд — оптимальный интервал, чтобы увидеть «гребёнку», а не сглаженное среднее.

P.P.S. Скрипты проверены на Debian/Ubuntu. Нужен установленный curl и screen (для фонового режима).

Теги: #bash #TTFB #vps #мониторинг #оптимизация #хостинг
👍 Оцените статью
Всего голосов: 0



Категории:

Категории

Комментарии

Пока нет комментариев. Будьте первым!

Оставить комментарий

← Назад к списку

Посетителей сегодня: 0
о блоге | карта блога | 📡 Подписаться на RSS

© Digital Specialist | Не являемся сотрудниками Google, Яндекса и NASA