Анализ узких мест (Bottleneck Analysis) по результатам нагрузочных тестов

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Анализ узких мест (Bottleneck Analysis) по результатам нагрузочных тестов
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Анализ узких мест по результатам нагрузочного теста

Нагрузочный тест показал деградацию — осталось найти причину. Последовательный анализ: сетевой уровень → приложение → база данных → инфраструктура. Узкое место всегда одно: устранить его, запустить тест снова.

Диагностическая последовательность

Высокая latency или ошибки
        │
        ├── p95 latency высокая, CPU < 70%, memory ОК
        │   └── → База данных: медленные запросы, блокировки, N+1
        │
        ├── CPU 90–100%, latency растёт пропорционально
        │   └── → Вычислительный bottleneck: профилировать CPU-hot paths
        │
        ├── Memory растёт, swap активен
        │   └── → Утечка памяти или heap too small
        │
        ├── ENOMEM / EMFILE / ECONNREFUSED
        │   └── → Системные лимиты: ulimit, file descriptors, TCP backlog
        │
        └── Ошибки 502/504, приложение ОК
            └── → Nginx upstream, load balancer timeout

Анализ PostgreSQL под нагрузкой

-- Запущенные запросы прямо сейчас (выполнять во время теста)
SELECT pid, now() - query_start AS duration,
       state, wait_event_type, wait_event,
       left(query, 100) AS query_preview
FROM pg_stat_activity
WHERE state != 'idle'
  AND query NOT LIKE '%pg_stat_activity%'
ORDER BY duration DESC;

-- Блокировки: кто кого блокирует
SELECT blocked.pid, blocked.query,
       blocking.pid AS blocking_pid,
       blocking.query AS blocking_query
FROM pg_stat_activity blocked
JOIN pg_stat_activity blocking
  ON blocking.pid = ANY(pg_blocking_pids(blocked.pid))
WHERE blocked.cardinality(pg_blocking_pids(blocked.pid)) > 0;

-- Самые тяжёлые запросы (pg_stat_statements)
SELECT query, calls, mean_exec_time, total_exec_time,
       stddev_exec_time, rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

-- Missing indexes: sequential scans на больших таблицах
SELECT relname, seq_scan, seq_tup_read,
       idx_scan, seq_tup_read / nullif(seq_scan, 0) AS avg_rows_per_seqscan
FROM pg_stat_user_tables
WHERE seq_scan > 100
  AND seq_tup_read > 10000
ORDER BY seq_tup_read DESC;

Node.js: CPU профилировщик

// server.js — включить V8 profiling через сигнал
process.on('SIGUSR1', () => {
  const { Session } = require('inspector')
  const session = new Session()
  session.connect()

  session.post('Profiler.enable')
  session.post('Profiler.start')

  // Профилировать 30 секунд
  setTimeout(() => {
    session.post('Profiler.stop', (err, { profile }) => {
      require('fs').writeFileSync('./cpu-profile.cpuprofile', JSON.stringify(profile))
      console.log('CPU profile saved to cpu-profile.cpuprofile')
      session.disconnect()
    })
  }, 30000)
})

// Запустить под нагрузкой: kill -USR1 <pid>
// Открыть в Chrome DevTools → More Tools → JavaScript Profiler
# Альтернатива: 0x flamegraph
npm install -g 0x
0x --output-dir profile node server.js &
APP_PID=$!

# Запустить k6 тест
k6 run tests/load/main.js

# Остановить и получить flamegraph
kill -USR2 $APP_PID
# Откроется flamegraph.html в браузере

Flamegraph: что искать

Широкие плоские полосы в середине — долгая CPU-работа. Типичные находки:

  • JSON.parse/stringify в hot path — переключиться на streaming или schema-based serializer
  • bcrypt с высоким cost factor — снизить cost или кешировать сессии
  • Regex без компиляции — вынести за пределы функции
  • Синхронная файловая операция (fs.readFileSync) в запросе

Python: профилировщик под нагрузкой

# pyinstrument — non-intrusive profiler для production
pip install pyinstrument

# Middleware для Flask
from pyinstrument import Profiler
from flask import request, g

@app.before_request
def start_profiler():
    if request.args.get('profile') == 'true':
        g.profiler = Profiler()
        g.profiler.start()

@app.after_request
def stop_profiler(response):
    if hasattr(g, 'profiler'):
        g.profiler.stop()
        # Вернуть HTML-отчёт в ответе
        response.data = g.profiler.output_html()
        response.content_type = 'text/html'
    return response

# Запрос с профилированием: GET /api/posts?profile=true

Анализ connection pool

# Мониторинг pgBouncer во время теста
psql -h localhost -p 6432 pgbouncer -c "SHOW POOLS;"

# Что смотреть:
# cl_active: клиенты активно работают
# cl_waiting: клиенты ждут соединения (>0 = проблема)
# sv_active: серверные соединения активны
# sv_idle: простаивающие соединения в пуле
# maxwait: максимальное время ожидания (сек)
-- PostgreSQL: статистика пула соединений
SELECT datname, count(*) AS total_connections,
       count(*) FILTER (WHERE state = 'active') AS active,
       count(*) FILTER (WHERE state = 'idle') AS idle,
       count(*) FILTER (WHERE wait_event_type = 'Lock') AS waiting_lock
FROM pg_stat_activity
GROUP BY datname;

Анализ результатов k6: найти moment деградации

# parse_k6_results.py
import json
import pandas as pd

def find_degradation_point(json_results: str):
    """Найти момент деградации по временному ряду метрик"""
    records = []

    with open(json_results) as f:
        for line in f:
            try:
                record = json.loads(line)
                if record.get('type') == 'Point':
                    records.append({
                        'timestamp': record['data']['time'],
                        'metric': record['metric'],
                        'value': record['data']['value']
                    })
            except:
                continue

    df = pd.DataFrame(records)
    df['timestamp'] = pd.to_datetime(df['timestamp'])

    # Анализировать p95 latency по 1-минутным окнам
    p95_df = df[df['metric'] == 'http_req_duration'].copy()
    p95_df = p95_df.set_index('timestamp').resample('1min')['value'].quantile(0.95)

    # Найти первую минуту, когда p95 превысил порог
    threshold = 500  # мс
    degradation = p95_df[p95_df > threshold]

    if not degradation.empty:
        print(f"Degradation detected at: {degradation.index[0]}")
        print(f"p95 at degradation: {degradation.iloc[0]:.0f}ms")
    else:
        print("No degradation detected (all within threshold)")

    return p95_df

Типичные оптимизации после анализа

Узкое место Симптом Решение
N+1 запросы к БД DB active queries >> VU count DataLoader / eager loading / JOIN
Отсутствующий индекс SeqScan на большой таблице CREATE INDEX CONCURRENTLY
Медленный JSON serialize CPU высокий, hot path в serialize Protobuf / simdjson / msgpack
Connection pool overflow cl_waiting > 0 в pgBouncer Увеличить pool_size или добавить replicas
GC паузы Spiky latency без CPU нагрузки Увеличить heap, tune GC flags
Блокировки на таблицах wait_event = Lock в pg_stat Оптимизировать порядок операций, NOWAIT

Срок выполнения

Полный анализ узких мест по результатам нагрузочного теста с рекомендациями и верификационным тестом — 1–2 рабочих дня.