Диагностика неочевидных проблем производительности серверов для SaaS КлаудАпп

Загадочные тормоза без видимых причин

SaaS-платформа «КлаудАпп» (онлайн-конструктор документов, 12 000 активных пользователей) начала необъяснимо тормозить. Мониторинг показывал нормальную картину: CPU 40%, RAM 60%, диски без очередей, сеть свободна. Но пользователи жаловались на задержки от 2 до 15 секунд при сохранении документов.

Команда клиента потратила две недели на поиск проблемы: перезапускали контейнеры, увеличивали RAM, добавляли реплики — безрезультатно. После обращения к itfresh.ru мы начали системную диагностику по методологии USE (Utilization, Saturation, Errors) Брендана Грегга.

Инфраструктура: 6 VPS у облачного провайдера (4 vCPU, 16 GB RAM каждый), Kubernetes, PostgreSQL на выделенном инстансе. Приложение — Python/Django + React.

Проблема 1: CPU steal time — шумные соседи

Первое, что мы проверили — не стандартное потребление CPU, а его структуру. Команда top показывает общий процент, но не показывает главное — steal time:

# Смотрим детальную статистику CPU
mpstat -P ALL 1 10

# Результат на одном из серверов:
# CPU    %usr   %nice  %sys  %iowait  %steal  %idle
# all    28.3    0.0    5.1    1.2     12.4    53.0
# 0      31.2    0.0    6.3    0.8     14.1    47.6
# 1      25.4    0.0    3.9    1.6     10.7    58.4

12.4% steal time — это CPU-время, которое гипервизор отбирает у нашей виртуальной машины в пользу других VM на том же физическом хосте. При пиковой нагрузке steal достигал 25-30%, что объясняло рандомные задержки.

# Мониторим steal в реальном времени
sar -u 1 60 | awk '$NF ~ /[0-9]/ && $8 > 5 {print strftime("%H:%M:%S"), "steal:", $8"%"}'

Решение: миграция двух наиболее нагруженных VPS на dedicated vCPU инстансы (с гарантированными ядрами). Steal time упал до 0.1-0.3%. Стоимость выросла на 40%, но p99 латентность снизилась в 3 раза.

Проблема 2: NUMA и неоптимальное распределение памяти

На выделенном сервере PostgreSQL (2 сокета, 32 ядра, 128 GB RAM) мы обнаружили неожиданно высокую латентность запросов при незагруженной системе. Причина — NUMA (Non-Uniform Memory Access):

# Проверяем NUMA-топологию
numactl --hardware

# Результат:
# available: 2 nodes (0-1)
# node 0 cpus: 0-15
# node 0 size: 65536 MB
# node 0 free: 12340 MB
# node 1 cpus: 16-31
# node 1 size: 65536 MB
# node 1 free: 48210 MB

# Проверяем NUMA-промахи
numastat -p $(pgrep -x postgres | head -1)

# numa_miss: 4 821 304  ← обращения к памяти на чужой ноде

PostgreSQL запускался без привязки к NUMA-ноде. Процессы на CPU 0-15 обращались к памяти на ноде 1 — это на 30-50% медленнее, чем обращение к локальной памяти.

# Привязываем PostgreSQL к ноде 0 (процессор + локальная память)
# В systemd-юните:
# ExecStart=numactl --cpunodebind=0 --membind=0 /usr/lib/postgresql/16/bin/postgres ...

# Или через конфигурацию systemd:
# [Service]
# NUMAPolicy=bind
# NUMAMask=0

# Проверяем эффект
numastat -p $(pgrep -x postgres | head -1)
# numa_miss: 0  ← все обращения к локальной памяти

Результат: средняя латентность запросов PostgreSQL снизилась на 18%, а p99 — на 35%.

Проблема 3: Transparent Huge Pages убивают латентность

Django-воркеры периодически «замирали» на 50-200 мс без видимой причины. Профилирование с perf показало неожиданного виновника:

# Профилируем Python-процесс
perf record -g -p $(pgrep -f 'gunicorn.*worker') -- sleep 30
perf report --stdio | head -40

# Результат:
# 23.4%  python  [kernel]  [k] compact_zone_order
# 18.1%  python  [kernel]  [k] __alloc_pages_slowpath
#  9.7%  python  [kernel]  [k] khugepaged

23% CPU-времени уходит на memory compaction — ядро пытается создать Transparent Huge Pages (THP), дефрагментируя память в реальном времени. Для баз данных и приложений с частыми аллокациями THP приносит больше вреда, чем пользы.

# Проверяем текущее состояние THP
cat /sys/kernel/mm/transparent_hugepage/enabled
# [always] madvise never

cat /sys/kernel/mm/transparent_hugepage/defrag
# [always] defer defer+madvise madvise never

# Отключаем THP
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

# Делаем постоянным через systemd
cat > /etc/systemd/system/disable-thp.service << 'EOF'
[Unit]
Description=Disable Transparent Huge Pages
DefaultDependencies=no
After=sysinit.target local-fs.target
Before=basic.target

[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/enabled && echo never > /sys/kernel/mm/transparent_hugepage/defrag'

[Install]
WantedBy=basic.target
EOF
systemctl daemon-reload
systemctl enable disable-thp

После отключения THP периодические задержки в 50-200 мс полностью исчезли. Это одна из самых распространённых и при этом неочевидных причин нестабильной латентности.

Проблема 4: disk I/O scheduler и сетевые буферы

Один из серверов показывал высокую iowait при записи логов, хотя NVMe-диск был способен на 500K IOPS. Проверяем планировщик I/O:

# Текущий планировщик
cat /sys/block/nvme0n1/queue/scheduler
# [mq-deadline] kyber bfq none

# Для NVMe оптимален none (без планировщика)
echo none > /sys/block/nvme0n1/queue/scheduler

# Глубина очереди
cat /sys/block/nvme0n1/queue/nr_requests
# 32  ← слишком мало для NVMe
echo 1024 > /sys/block/nvme0n1/queue/nr_requests

Сетевые буферы — ещё один скрытый bottleneck. При пиковой нагрузке пакеты терялись из-за переполнения ring buffer:

# Проверяем текущие потери
ethtool -S eth0 | grep -i drop
#   rx_dropped: 48721
#   tx_dropped: 0

# Текущий размер ring buffer
ethtool -g eth0
# Pre-set maximums:
#   RX: 4096
#   TX: 4096
# Current hardware settings:
#   RX: 256   ← слишком мало
#   TX: 256

# Увеличиваем
ethtool -G eth0 rx 4096 tx 4096

# IRQ affinity — распределяем обработку прерываний по ядрам
# Устанавливаем irqbalance или делаем вручную:
apt install -y irqbalance
systemctl enable --now irqbalance

Фиксируем все параметры через sysctl:

# /etc/sysctl.d/99-performance.conf

# Сетевые буферы
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.netdev_max_backlog = 16384
net.core.somaxconn = 8192

# TCP оптимизация
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_mtu_probing = 1

# Виртуальная память
vm.dirty_ratio = 10
vm.dirty_background_ratio = 5
vm.dirty_expire_centisecs = 500

Flamegraphs и bpftrace: находим микрозадержки

Для поиска оставшихся микрозадержек мы применили инструменты продвинутой диагностики — flamegraph и bpftrace.

Flamegraph показывает, на что тратится CPU-время в виде наглядной иерархии вызовов:

# Собираем данные perf за 60 секунд
perf record -F 99 -ag -- sleep 60

# Генерируем flamegraph
git clone https://github.com/brendangregg/FlameGraph.git
perf script | ./FlameGraph/stackcollapse-perf.pl | \
    ./FlameGraph/flamegraph.pl > flamegraph.svg

Flamegraph выявил, что 8% CPU-времени Django-воркеров уходит на сериализацию JSON через стандартный модуль json. Замена на orjson дала ускорение сериализации в 6 раз.

Bpftrace — для трассировки конкретных системных вызовов:

# Измеряем латентность fsync (критично для PostgreSQL)
bpftrace -e '
  tracepoint:syscalls:sys_enter_fsync { @start[tid] = nsecs; }
  tracepoint:syscalls:sys_exit_fsync /@start[tid]/ {
    @us = hist((nsecs - @start[tid]) / 1000);
    delete(@start[tid]);
  }
  interval:s:10 { exit(); }
'

# Результат — гистограмма латентности fsync:
# @us:
# [1, 2)     1205 |@@@@@@@@@@@@@@@@@@@@|
# [2, 4)      834 |@@@@@@@@@@@@@@@     |
# [4, 8)      312 |@@@@@               |
# [8, 16)      47 |@                   |
# [16, 32)     12 |                    |
# [32, 64)      3 |                    |  ← выбросы, требуют внимания
# Отслеживаем процессы с наибольшим количеством context switch
bpftrace -e '
  tracepoint:sched:sched_switch {
    @[comm] = count();
  }
  interval:s:5 { print(@); clear(@); }
'

Bpftrace показал, что Node.js-процессы, обрабатывающие WebSocket-соединения, делали избыточное количество context switch (85 000/сек) из-за неоптимального event loop. После тюнинга UV_THREADPOOL_SIZE и объединения мелких записей в буфер количество переключений снизилось до 12 000/сек.

Методология USE/RED и системный подход

Разрозненная диагностика отдельных компонентов неэффективна. Мы внедрили для «КлаудАпп» две методологии мониторинга:

USE Method (для инфраструктуры) — для каждого ресурса (CPU, память, диск, сеть, мьютексы) проверяем три показателя:

  • Utilization — процент использования (CPU usage, disk usage)
  • Saturation — очередь ожидания (run queue, disk queue depth)
  • Errors — ошибки (network drops, disk errors, OOM events)

Чеклист для быстрой диагностики сервера:

# USE Method — быстрый обход за 60 секунд

# CPU: utilization + saturation
mpstat -P ALL 1 3       # %usr + %sys = utilization; %steal = чужой steal
sar -q 1 3              # runq-sz = saturation (>2x CPU count = проблема)

# Memory: utilization + saturation + errors
free -h                 # utilization
vmstat 1 5              # si/so = swap in/out (saturation)
dmesg | grep -i oom     # errors

# Disk: utilization + saturation
iostat -xz 1 3          # %util = utilization; avgqu-sz = saturation

# Network: utilization + errors
sar -n DEV 1 3          # rxkB/s, txkB/s = utilization
ip -s link show eth0    # RX/TX errors, dropped = errors
ethtool -S eth0 | grep -i err

RED Method (для сервисов) — для каждого сервиса отслеживаем:

  • Rate — количество запросов в секунду
  • Errors — процент ошибок (5xx, таймауты)
  • Duration — время ответа (p50, p95, p99)

Мы настроили Grafana-дашборды с USE и RED для каждого сервера и сервиса. Теперь при любой жалобе на «тормоза» команда «КлаудАпп» за 2 минуты определяет, где проблема — в инфраструктуре (USE) или в приложении (RED).

Результаты диагностики и оптимизации

Итого мы обнаружили и устранили 6 скрытых проблем производительности:

ПроблемаВлияниеРешениеЭффект
CPU steal timep99 +300%Dedicated vCPUp99 -67%
NUMA промахиDB латентность +30%numactl bindDB p99 -35%
THP compactionПаузы 50-200 мсОтключение THPПаузы исчезли
I/O scheduleriowait при записиnone + nr_requestsiowait -80%
Network ring buffer48K dropped пакетовRing buffer 40960 dropped
JSON сериализация8% CPUorjson вместо jsonCPU -7%

Суммарный эффект:

  • p50 латентность: 180 мс → 65 мс
  • p99 латентность: 4 200 мс → 280 мс
  • Жалобы пользователей на «тормоза»: с 25 в неделю до 1-2 в месяц
  • Снижение общего потребления CPU: на 15%, что позволило отказаться от двух из шести VPS

Ключевой урок проекта: стандартный мониторинг (CPU%, RAM%, disk%) недостаточен. Проблемы кроются в деталях — steal time, NUMA topology, THP, ring buffers. Специалисты itfresh.ru рекомендуют включать расширенные метрики (mpstat, numastat, perf) в стандартный набор мониторинга для любого production-сервера.

Часто задаваемые вопросы

Команда mpstat 1 5 — смотрите колонку %steal. Значение выше 5% на постоянной основе — повод обращаться к хостеру или мигрировать на инстанс с гарантированными ядрами. Пиковые значения до 10% допустимы на shared-инстансах.
Для баз данных (PostgreSQL, MySQL, MongoDB, Redis) — однозначно да. Для приложений с частыми аллокациями (Python, Node.js) — тоже рекомендуется. THP полезен только для вычислительных задач с большими непрерывными блоками памяти (HPC, научные расчёты).
USE — методология Брендана Грегга: для каждого ресурса (CPU, RAM, диск, сеть) проверяем Utilization (% использования), Saturation (очередь ожидания) и Errors (ошибки). Проход по всем ресурсам занимает 60 секунд и выявляет 80% типовых проблем производительности.
Perf record с флагом -F 99 (99 Гц сэмплинг) добавляет менее 1% overhead и безопасен для продакшена. Bpftrace тоже безопасен — он использует eBPF, который проверяется ядром перед выполнением. Избегайте только tracepoint с высокой частотой срабатывания (миллионы/сек) — они могут замедлить систему.
Для NVMe-дисков оптимален scheduler none — у NVMe собственные аппаратные очереди, и программный планировщик добавляет лишний overhead. Для обычных SSD/HDD — mq-deadline. Для десктопов с HDD — bfq (приоритизирует интерактивные процессы).

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#performance#cpu steal#numa#transparent huge pages#io scheduler#irq affinity#perf#bpftrace
Комментарии 0

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

загрузка...