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

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

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

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

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

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

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

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

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

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

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

Нагрузочный тест показал падение: 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 в месяц.