Полный анализ узких мест: как найти bottleneck в нагрузочном тесте

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

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

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Полный анализ узких мест: как найти bottleneck в нагрузочном тесте
Средний
~2-3 дня
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1358
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1251
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    956
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1188
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    929
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    947

Как последовательно выявить узкое место в производительности

Нагрузочный тест показал падение: latency взлетела с 50 ms до 2000 ms, а логи молчат. Ситуация знакомая — мы видели её на десятках проектов. В одном случае причиной оказался медленный JSON.parse в горячем пути: замена на simdjson снизила p95 latency с 200 ms до 30 ms — экономия на инфраструктуре составила более 60%. Подход один: измерить → найти → устранить → повторить.

Диагностический фреймворк: от метрик к узкому месту

Первым делом смотрим на метрики верхнего уровня. Если p95 latency высокая, а CPU загружен менее 70% — ищем проблему в БД или внешних вызовах. Если CPU 90–100% — профилируем код. Если растёт memory и идёт swap — ищем утечку. Системные ошибки (ENOMEM, EMFILE) — проверяем лимиты ОС. Ошибки 502/504 — смотрим балансировщик.

Высокая 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

Оптимизация базы данных: медленные запросы и N+1

Как найти медленные запросы в PostgreSQL?

Во время нагрузочного теста выполняем следующие запросы. Первый покажет активные запросы с длительностью. Второй — блокировки (кто кого ждёт). Третий — самые тяжёлые запросы по aggregate времени. Четвёртый — таблицы с sequential scans (потенциальные missing indexes).

-- Запущенные запросы прямо сейчас (выполнять во время теста)
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;

Почему N+1 запросы — частая причина деградации?

N+1 возникает, когда ORM для каждого родительского объекта выполняет отдельный запрос к связанной таблице. При 1000 пользователях это 1001 запрос вместо одного JOIN. Симптом: количество active connections в БД равно числу виртуальных пользователей, а pg_stat_statements показывает один и тот же запрос с большим количеством вызовов. Решение — eager loading, DataLoader или ручной JOIN. Использование pg_stat_statements для выявления N+1 в 10 раз быстрее ручного анализа логов.

Профилирование приложения: Node.js, Python и connection pool

Профилирование CPU в Node.js

Самый простой способ — включить V8 profiler через сигнал. Запускаем код под нагрузкой, отправляем kill -USR1 <pid>, через 30 секунд получаем cpu-profile.cpuprofile, открываем в Chrome DevTools.

// 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

Альтернатива — flamegraph через утилиту 0x. Она собирает стектрейсы и рисует интерактивный граф, где ширина полосы — время выполнения. Типичные находки: JSON.parse/stringify в hot path, bcrypt с высоким cost factor, некешированные regex, синхронные файловые операции.

npm install -g 0x
0x --output-dir profile node server.js &
APP_PID=$!
k6 run tests/load/main.js
kill -USR2 $APP_PID
# Откроется flamegraph.html

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

Для production используем pyinstrument — он не требует перезапуска и даёт детальный отчёт по каждому запросу. Добавляем middleware, которая включает профилирование по параметру ?profile=true.

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()
        response.data = g.profiler.output_html()
        response.content_type = 'text/html'
    return response

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

Анализ connection pool

Если клиенты ждут соединения (cl_waiting > 0 в pgBouncer), пул мал. Проверяем через SHOW POOLS; или SQL-запросом к pg_stat_activity.

-- 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: как найти момент деградации?

Скрипт анализирует временной ряд p95 latency и находит первую минуту, когда значение превысило порог (например, 500 мс). Это помогает привязать деградацию к конкретному моменту теста.

Скрипт для анализа временных рядов k6 (нажмите, чтобы развернуть)
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_df = df[df['metric'] == 'http_req_duration'].copy()
    p95_df = p95_df.set_index('timestamp').resample('1min')['value'].quantile(0.95)
    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

Инструменты и типичные оптимизации

Сравнение инструментов профилирования

Инструмент Область Глубина Влияние на production
pg_stat_statements PostgreSQL Высокая Нет
V8 profiler Node.js Высокая Минимальное
pyinstrument Python Средняя Нет
0x Node.js Высокая Требует перезапуска
flamegraph Общая Высокая Зависит от сборщика

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

Узкое место Симптом Решение
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

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

После анализа мы предоставляем:

  • Отчёт с графиками временных рядов (latency, throughput, CPU, memory, БД-метрики)
  • Скрипты для воспроизведения нагрузки
  • Детальный список узких мест с кодом и конфигами
  • Рекомендации по оптимизации (приоритет, трудозатраты)
  • Верификационный тест после внедрения изменений
  • Консультацию по архитектуре для предотвращения будущих проблем

Полный анализ с отчётом и верификацией занимает 1–2 рабочих дня. Стоимость рассчитывается индивидуально в зависимости от сложности системы. Свяжитесь с нами — мы оценим ваш проект и подберём оптимальный формат работы. Наши инженеры имеют 10+ лет опыта в нагрузочном тестировании и оптимизации. Средняя экономия на облачных ресурсах после наших оптимизаций составляет от $3000 до $10000 в месяц.

Почему юнит-тесты важны, но не панацея?

Баг, найденный юнит-тестом, стоит минуты исправления. Тот же баг в продакшене — часы инцидента, компенсации и потеря доверия. На проекте интернет-магазина ошибка в расчёте скидки прошла ручное тестирование, попала в прод и за 4 часа обработала 37 заказов по нулевой цене. Автотест на граничные случаи расчёта поймал бы её при первом же push. Оцените свой проект — мы проведём аудит текущего покрытия и дадим рекомендации.

Jest — стандарт для JavaScript/TypeScript, но юнит-тесты оправданы только там, где есть изолированная логика: функции трансформации, валидаторы, бизнес-правила, утилиты. Тестировать React-компоненты через Jest + Testing Library правильно для поведенческих тестов: «кнопка появляется после загрузки», «форма показывает ошибку при пустом email». Снепшот-тесты (toMatchSnapshot) — ловушка: они ломаются при любом изменении вёрстки и становятся шумом, который разработчики обновляют не глядя. Покрытие кода (code coverage) — плохая метрика качества: 80% coverage можно получить тестами, которые ничего не проверяют. Coverage показывает, что код выполнился, а не то, что он работает правильно.

Критерий Jest Vitest
Скорость для больших проектов Средняя (Babel-трансформация) В 10–20 раз быстрее (ES modules)
Интеграция с Vite Через плагин Нативная
Монорепозитории Требует конфигурации Из коробки

Vitest как альтернатива Jest для Vite-проектов: в 10–20 раз быстрее за счёт нативных ES modules без трансформации через Babel. Для монорепозиториев с тысячами тестов разница в скорости ощутима. Подробнее о юнит-тестировании.

Как настроить E2E тесты, которые не будут flaky?

Playwright обошёл Cypress по ключевым параметрам: нативная поддержка multi-tab, multi-origin, iframe; параллельное выполнение на уровне тестов; WebKit, Firefox, Chromium из коробки; нет iframe для приложения — тесты работают в реальном браузере.

Playwright codegen записывает действия и генерирует тест — хорошая точка старта, но сгенерированный код нужно рефакторить. Локаторы по text content хрупки: getByRole('button', { name: 'Оформить заказ' }) — устойчивее, чем locator('.btn-primary').

Page Object Model — стандарт организации E2E тестов. Каждая страница — отдельный класс с методами вместо прямых локаторов. Когда кнопка переехала из хедера в сайдбар — меняем в одном месте, не ищем по всем тестам.

Как избежать flaky тестов? Типичная проблема — flaky tests. Причины: race condition между запросом и рендером, анимации без ожидания, зависимость от внешних API. Решение: `page.waitForResponse()` вместо `page.waitForTimeout()`, мокирование внешних API через `page.route()`.
// Плохо
await page.click('#submit');
await page.waitForTimeout(2000);
await expect(page.locator('.success')).toBeVisible();

// Хорошо
await page.click('#submit');
await page.waitForResponse(resp =>
  resp.url().includes('/api/orders') && resp.status() === 201
);
await expect(page.getByRole('alert', { name: /заказ создан/i })).toBeVisible();

Наши инженеры гарантируют стабильность тестов в CI. Документация Playwright — основной инструмент на проектах с миллионами пользователей.

Нагрузочное тестирование с k6

k6 — инструмент для нагрузочного тестирования с JavaScript API. Сценарии пишутся как код, версионируются в git, запускаются в CI. Три основных сценария:

  • Spike test — резкий рост нагрузки: 0 → 1000 пользователей за 30 секунд. Имитирует запуск рекламной кампании. Показывает способность системы реагировать на пики.
  • Soak test — стабильная нагрузка на 2–4 часа. Выявляет memory leaks, connection pool exhaustion, деградацию производительности.
  • Stress test — нагрузка выше расчётной (150–200% от ожидаемого пика). Показывает точку отказа и graceful degradation.

Пороговые значения:

thresholds: {
  http_req_duration: ['p95<500', 'p99<1000'],
  http_req_failed: ['rate<0.01'],
}

p95 < 500ms означает: 95% запросов отвечают быстрее полусекунды. Если порог не выполняется — k6 завершается с кодом ошибки, CI-пайплайн падает.

На одном проекте интернет-магазина мы выявили деградацию API на 4-й час теста: p95 вырос с 200ms до 2s из-за утечки соединений. После оптимизации клиент сэкономил около $15,000 в год на инцидентах и лишних ресурсах. Получите аналогичный аудит вашего проекта — закажите нагрузочное тестирование.

Как Core Web Vitals влияют на ранжирование?

Google использует Core Web Vitals в ранжировании. Lighthouse CLI в CI-пайплайне: при каждом деплое проверяем, что LCP < 2.5s, CLS < 0.1, INP < 200ms. Подробнее о веб-производительности. Реальные проблемы, которые Lighthouse находит:

  • Hero image без width/height атрибутов: CLS 0.35 при загрузке.
  • JavaScript-бандл 2.1MB синхронно блокирует парсинг: INP 450ms.
  • Шрифты без font-display: swap: невидимый текст до загрузки шрифта (FOIT).
  • Неоптимизированный hero image 4MB: LCP 8.2s.

Lighthouse CI (lhci) сохраняет историю метрик и отправляет комментарий к PR с деградацией. По данным Google, 53% пользователей покидают сайт при загрузке дольше 3 секунд — наши тесты предотвращают такие потери.

Пирамида тестирования в проекте

Уровень Инструмент Количество Скорость
Юнит Vitest/Jest Много (тысячи) <5 мин
Интеграция Vitest + supertest Среднее 5–15 мин
E2E Playwright Немного (happy path) 10–30 мин
Нагрузка k6 По расписанию 30–60 мин
Performance Lighthouse CI При каждом деплое 5 мин

Что входит в работу?

  • Аудит текущего покрытия и определение критических user flows.
  • Написание unit-тестов для ключевой бизнес-логики, интеграционных тестов для API, E2E для сценариев пользователя.
  • Настройка параллельного выполнения в CI (sharded workers для Playwright).
  • Нагрузочное тестирование с отчётом и рекомендациями.
  • Документация по тест-кейсам, обучение вашей команды работе с тестами.
  • Гарантийная поддержка 1 месяц после внедрения.

Процесс работы

  1. Аналитика — аудит текущего тестирования, выявление слабых мест, определение приоритетов.
  2. Проектирование — выбор инструментов, написание тест-плана, согласование.
  3. Реализация — написание тестов, интеграция в CI.
  4. Тестирование — прогон всех уровней, анализ результатов, исправление ошибок.
  5. Деплой — запуск в прод, мониторинг метрик, обучение команды.

Сроки

Настройка полного тест-пайплайна (Jest + Playwright + k6 + Lighthouse CI) с нуля: 2–4 недели. Покрытие E2E-тестами существующего проекта (20–30 сценариев): 3–6 недель. Нагрузочное тестирование с отчётом и рекомендациями: 1–2 недели. Стоимость рассчитывается индивидуально после аудита.

Готовы обсудить ваш проект? Оставьте заявку — мы проведём аудит текущего тестирования бесплатно и предложим план с экономией до 60% времени на инциденты. Получите консультацию по тестированию веб-приложений — напишите нам.