Мониторинг производительности - практическое руководство для разработчиков

05 января 2026
Автор

Олег Марков

Введение

Мониторинг производительности (performance monitoring) — это не просто набор графиков и дашбордов. Это системный подход, который помогает вам понимать, как реально работает ваше приложение под нагрузкой, где оно тратит время и ресурсы, и почему пользователи иногда видят «крутящийся спиннер» вместо быстрого отклика.

Если вы когда-либо:

  • оптимизировали запрос «на глаз»;
  • пытались угадать, где «тормозит» система;
  • ловили проблемный релиз уже в продакшене;

то вам нужен продуманный мониторинг производительности.

Давайте разберемся, что именно мониторить, как это делать технически и какие инструменты использовать, чтобы быстрее находить и устранять узкие места.


Основные цели мониторинга производительности

Зачем он нужен разработчику и команде

Мониторинг производительности решает несколько практических задач:

  • Раннее обнаружение деградаций
    Например, время ответа API выросло с 100 до 400 мс — вы увидите это до того, как пользователи начнут массово жаловаться.

  • Поиск узких мест
    Понимание, где именно теряется время: в базе данных, в очереди, в внешнем API, в GC, в сетевом слое и т.д.

  • Подготовка к росту нагрузки
    Вы можете оценить, как система ведет себя при 100, 1000 и 10 000 запросов в секунду, и что нужно масштабировать.

  • Подтверждение эффектов оптимизаций
    Любая оптимизация должна быть измерима: до и после. Мониторинг дает цифры, а не ощущения.

  • Поддержка SLO / SLA
    Если вы обещаете, что 95 процентов запросов обрабатываются быстрее 300 мс, вам нужны метрики, подтверждающие это в реальном времени.


Типы мониторинга производительности

Инфраструктурный мониторинг

Здесь вы следите за состоянием серверов, контейнеров, сетей:

  • загрузка CPU;
  • использование памяти;
  • диск (IOPS, latency, заполненность);
  • сеть (пропускная способность, ошибки, задержки);
  • состояние контейнеров / pod’ов.

Этот уровень отвечает на вопрос:
«Хватает ли ресурсов машине, на которой все крутится?»

Мониторинг приложений (APM)

APM (Application Performance Monitoring) позволяет увидеть:

  • время обработки запросов (endpoint’ы, методы, маршруты);
  • трассировки (traces) — поэтапный путь запроса через сервисы;
  • медленные SQL-запросы;
  • ошибки и исключения;
  • потребление ресурсов именно приложением (CPU, память, GC и т.д.).

Здесь вы отвечает на вопрос:
«Где именно в коде или в цепочке микросервисов тратится время?»

Мониторинг баз данных

Вы смотрите:

  • время исполнения запросов;
  • план выполнения (explain);
  • блокировки (locks);
  • кеширование запросов;
  • нагрузку на диск и буферный кеш.

Этот уровень отвечает:
«Почему база данных отвечает медленно и какие запросы в этом виноваты?»

Мониторинг с точки зрения пользователя (RUM, Synthetic)

Есть два подхода:

  • RUM (Real User Monitoring) — сбор данных прямо из браузера или мобильного приложения:

    • TTFB (time to first byte);
    • FCP (First Contentful Paint);
    • LCP (Largest Contentful Paint);
    • CLS, FID, INP и другие Web Vitals.
  • Synthetic monitoring — роботы сами ходят по вашему сайту / API:

    • делают регулярные проверки;
    • измеряют время ответа;
    • проверяют доступность (uptime).

Здесь вы видите:
«Что реально видит пользователь и насколько быстро все работает для него?»


Ключевые метрики производительности

Время ответа (latency) и хвостовые задержки

Смотрите, это одна из главных метрик. Важно учитывать не только среднее значение, но и перцентили:

  • p50 — медиана, «типичный» запрос;
  • p90 — 90 процентов запросов быстрее этой величины;
  • p95, p99 — хвост, где живут самые медленные запросы.

Почему это важно:

  • Среднее время может быть 100 мс, но p99 — 2 секунды.
    Значит, 1 процент запросов сильно страдают.

В коде метрики обычно собирают с помощью библиотек вроде Prometheus client. Пример на Go:

// Импортируем клиент Prometheus
import "github.com/prometheus/client_golang/prometheus"

// Создаем гистограмму для измерения времени ответа HTTP
var httpRequestDuration = prometheus.NewHistogramVec(
    prometheus.HistogramOpts{
        Name:    "http_request_duration_seconds", // Имя метрики
        Help:    "Время обработки HTTP запросов",
        Buckets: prometheus.DefBuckets,           // Стандартные интервалы
    },
    []string{"method", "path", "status"}, // Метки для детализации
)

func init() {
    // Регистрируем метрику в Prometheus
    prometheus.MustRegister(httpRequestDuration)
}

// Обертка над HTTP обработчиком, которая измеряет время запроса
func InstrumentHandler(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now() // Фиксируем начало обработки
        ww := &statusWriter{ResponseWriter: w, status: 200}
        next.ServeHTTP(ww, r)               // Вызываем реальный обработчик
        elapsed := time.Since(start).Seconds()

        // Записываем измеренное время в гистограмму
        httpRequestDuration.
            WithLabelValues(r.Method, r.URL.Path, strconv.Itoa(ww.status)).
            Observe(elapsed)
    })
}

Как видите, этот код оборачивает существующий HTTP-обработчик и автоматически собирает время ответа.

Пропускная способность (throughput) и RPS

Пропускная способность — это количество операций в единицу времени, например:

  • RPS (requests per second);
  • сообщений в очереди в секунду;
  • операций чтения / записи в базу.

В Prometheus такую метрику обычно измеряют счетчиком и потом берут rate. Пример:

// Счетчик всех HTTP запросов
var httpRequestsTotal = prometheus.NewCounterVec(
    prometheus.CounterOpts{
        Name: "http_requests_total",       // Общее число запросов
        Help: "Счетчик HTTP запросов",
    },
    []string{"method", "path", "status"},
)

func init() {
    prometheus.MustRegister(httpRequestsTotal)
}

func InstrumentHandler(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ww := &statusWriter{ResponseWriter: w, status: 200}
        next.ServeHTTP(ww, r)

        // Увеличиваем счетчик запросов
        httpRequestsTotal.
            WithLabelValues(r.Method, r.URL.Path, strconv.Itoa(ww.status)).
            Inc()
    })
}

Потом в PromQL вы можете посчитать RPS:

rate(http_requests_total[1m])

Использование CPU и памяти

Эти метрики можно смотреть:

  • на уровне ОС (node exporter, cAdvisor);
  • на уровне процесса / приложения (профилирование, runtime метрики).

Например, в Go можно включить экспорт runtime-метрик:

import "github.com/prometheus/client_golang/prometheus/collectors"

func init() {
    // Экспорт статистики runtime - GC, горутины, аллокации
    prometheus.MustRegister(collectors.NewGoCollector())
}

Теперь вы увидите:

  • go_goroutines — число горутин;
  • go_memstats_alloc_bytes — текущие аллокации;
  • go_gc_duration_seconds — время сборки мусора.

Ошибки и таймауты

Без метрики ошибок мониторинг производительности будет неполным. Важно отслеживать:

  • долю запросов с 4xx / 5xx;
  • количество исключений в приложении;
  • количество таймаутов при обращении к внешним сервисам.

Пример счетчика ошибок:

var httpErrorsTotal = prometheus.NewCounterVec(
    prometheus.CounterOpts{
        Name: "http_errors_total",
        Help: "Количество HTTP ошибок по статус коду",
    },
    []string{"status", "path"},
)

func init() {
    prometheus.MustRegister(httpErrorsTotal)
}

func countError(status int, path string) {
    // Если статус код 4xx или 5xx - считаем это ошибкой
    if status >= 400 {
        httpErrorsTotal.
            WithLabelValues(strconv.Itoa(status), path).
            Inc()
    }
}

Инструменты мониторинга производительности

Prometheus и Grafana

Это сочетание чаще всего используют для backend-систем и микросервисов.

  • Prometheus:

    • сам опрашивает приложения по HTTP endpoint’у /metrics;
    • хранит временные ряды (time series);
    • поддерживает язык запросов PromQL.
  • Grafana:

    • строит дашборды по данным Prometheus;
    • позволяет настраивать алерты и визуализацию.

Схема выглядит так:

  1. Вы добавляете в приложение Prometheus-клиент.
  2. Приложение отдает метрики по HTTP.
  3. Prometheus периодически «scrape-ит» эти метрики.
  4. Grafana подключается к Prometheus и рисует графики.

Пример фрагмента конфигурации Prometheus:

scrape_configs:
  - job_name: "my_service"          # Имя задания
    scrape_interval: 15s            # Как часто забирать метрики
    static_configs:
      - targets: ["my-service:9090"]  # Адрес сервиса с /metrics

APM: Jaeger, Zipkin, OpenTelemetry, коммерческие решения

Если вам нужно видеть, как один запрос проходит через десяток микросервисов, используется распределенное трассирование.

Подход:

  1. На входе в систему вы создаете trace-id.
  2. Каждая часть обработки добавляет свой span (фрагмент трассы).
  3. Все данные отправляются в APM-систему.

Сегодня стандартный путь — OpenTelemetry:

  • вы добавляете SDK в свое приложение;
  • оно автоматически создает спаны для HTTP-запросов, SQL-запросов и т.п.;
  • данные уходят в бекенд (Jaeger, Tempo, коммерческий APM и т.д.).

Пример на Go с OpenTelemetry (упрощенный):

import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/trace"
)

// Инициализируем глобальный tracer
var tracer trace.Tracer = otel.Tracer("my-service")

func handler(w http.ResponseWriter, r *http.Request) {
    // Начинаем новый span вокруг обработки HTTP запроса
    ctx, span := tracer.Start(r.Context(), "http_handler")
    defer span.End()

    // Передаем контекст дальше - все вложенные вызовы смогут продолжить trace
    processRequest(ctx)

    w.WriteHeader(http.StatusOK)
    _, _ = w.Write([]byte("ok"))
}

func processRequest(ctx context.Context) {
    // Внутренний span - например вызов базы данных
    _, span := tracer.Start(ctx, "db_query")
    defer span.End()

    // Здесь вы делаете запрос в базу - но сам запрос я опускаю
}

Теперь вы увидите в Jaeger/Tempo цепочку спанов и сразу поймете, где именно «застрял» запрос.

Логирование и корреляция с метриками

Мониторинг и логи работают лучше всего вместе:

  • логи дают контекст — что именно произошло;
  • метрики показывают, насколько часто и как это влияет на производительность.

Полезный подход — добавлять в лог trace-id или request-id, чтобы можно было:

  • увидеть метрику (время ответа);
  • перейти в трассу;
  • перейти в логи по этому же запросу.

Настройка мониторинга на практике

Шаг 1. Выбор ключевых метрик (SLO)

Для начала ответьте на вопрос:
«Что значит, что система работает хорошо?»

Примеры:

  • 99 процентов запросов GET /api/orders обрабатываются быстрее 300 мс;
  • Доля ошибок 5xx меньше 0.5 процента;
  • Время ответа базы данных p95 не превышает 50 мс.

Эти значения вы оформляете как SLO (service level objectives), а на их основе строите:

  • дашборды;
  • алерты.

Шаг 2. Инструментирование кода

Здесь я покажу общий подход, независимо от языка:

  1. Выделяете основные точки:

    • входные HTTP endpoint’ы;
    • вызовы базы данных;
    • внешние API;
    • очереди и фоновые задания.
  2. Для каждой точки:

    • измеряете время выполнения;
    • увеличиваете счетчик успешных/ошибочных операций;
    • добавляете теги/labels (тип операции, статус, ресурс).
  3. Проверяете, что метрики:

    • не содержат слишком много разных label-значений (cardinality);
    • не дублируют друг друга.

Шаг 3. Построение дашбордов

В Grafana удобно разбить дашборды:

  • Overview (обзор сервиса):

    • RPS;
    • latency p50/p90/p99;
    • error rate;
    • использование CPU / памяти.
  • API:

    • по endpoint’ам;
    • по статусам;
    • детализация по перцентилям.
  • База данных:

    • самые медленные запросы;
    • общее время, проведенное в БД;
    • блокировки.

Давайте посмотрим пример набора метрик для одного HTTP-сервиса:

  • http_requests_total с label’ами method, path, status;
  • http_request_duration_seconds с теми же label’ами;
  • go_goroutines, go_memstats_alloc_bytes и другие runtime-метрики;
  • db_query_duration_seconds с label’ами query_type, table.

Теперь у вас есть основа, чтобы строить аналитические панели.

Шаг 4. Настройка алертов

Мониторинг без алертов — это просто красивые графики.

Алерты должны быть:

  • привязаны к SLO;
  • четкими и понятными;
  • без «шума» и ложных срабатываний.

Пример алерта на рост времени ответа:

# Условие - p95 времени ответа больше 0.3с в течение 5 минут
histogram_quantile(0.95,
  sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
) > 0.3

Или алерт на долю ошибок:

# Ошибки 5xx за последние 5 минут больше 1 процента
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
> 0.01

Мониторинг производительности в микросервисной архитектуре

Проблемы, которые добавляют микросервисы

В монолите вы видите один процесс, одну базу, одну кодовую базу. В микросервисах появляется:

  • множество сервисов;
  • сетевые вызовы между ними;
  • очереди, брокеры сообщений;
  • разные языки и стеки.

Из-за этого:

  • проблемы возникают «на стыке» сервисов;
  • сложно понять, какой сервис на самом деле виноват в деградации;
  • увеличивается число мест, где нужно собирать метрики.

Что помогает в микросервисах

  1. Единая система метрик
    Например, везде Prometheus + единый набор базовых метрик.

  2. Распределенное трассирование
    OpenTelemetry с общим trace-id по всем сервисам.

  3. Корреляция логов и трасс
    Логируете trace-id и request-id в каждом сервисе.

  4. Единый формат / контракт метрик
    Например, каждый сервис:

    • имеет метрики http_requests_total, http_request_duration_seconds;
    • использует общие label’ы: service, endpoint, status.

Профилирование как часть мониторинга производительности

Чем отличается профилирование от мониторинга

  • Мониторинг — постоянно работающая система, с низкой нагрузкой, показывает тренды.
  • Профилирование — более детальный и «тяжелый» анализ, который вы включаете, когда надо глубже разобраться.

Профилирование отвечает на вопросы:

  • Какие функции занимают больше всего CPU?
  • Где происходят основные аллокации памяти?
  • Почему пошел пик GC?

Пример: pprof в Go

В Go есть встроенный пакет net/http/pprof. Покажу, как вы можете включить профили:

import (
    "log"
    "net/http"
    _ "net/http/pprof" // Импортируем pprof - он регистрирует обработчики
)

func main() {
    // Запускаем pprof сервер на localhost:6060
    go func() {
        log.Println("pprof listen on :6060")
        // http.DefaultServeMux уже содержит pprof endpoints
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()

    // Здесь запускается основное приложение - его код опущен
    startApp()
}

Теперь вы можете:

  • посмотреть профили в браузере: http://localhost:6060/debug/pprof/;
  • снять CPU-профиль через go tool pprof;
  • анализировать, какие функции занимают больше всего ресурсов.

Такое профилирование удобно сочетать с метриками:

  • по метрикам вы видите, что время ответа выросло;
  • включаете pprof и ищете, где именно CPU стал загружаться сильнее.

Практические советы по организации мониторинга

Что внедрять в первую очередь

Если вы только начинаете:

  1. Соберите:

    • время ответа (latency);
    • RPS;
    • долю ошибок (4xx, 5xx);
    • метрики CPU, памяти, диска.
  2. Добавьте дашборд «Обзор сервиса».

  3. Введите базовые алерты:

    • сервис недоступен (нет метрик / uptime);
    • p95 latency превышает порог;
    • error rate выше нормы.

После этого можно углубляться: база данных, распределенное трассирование, профилирование.

Как не утонуть в метриках

Смотрите, часто команды делают ошибку: добавляют слишком много метрик и label’ов. В итоге:

  • Prometheus начинает потреблять много памяти;
  • графики становятся медленными;
  • разобраться в лесу метрик сложно.

Лучше:

  • начинать с ограниченного набора;
  • стандартизировать имена и label’ы;
  • регулярно пересматривать, какие метрики действительно используются.

Мониторинг в CI / нагрузочном тестировании

Полезная практика — использовать те же инструменты мониторинга во время:

  • нагрузочных тестов;
  • pre-production окружений.

Вы можете:

  • запускать нагрузочные сценарии (например, k6, JMeter, Locust);
  • параллельно смотреть на метрики:
    • как растет latency при увеличении RPS;
    • в какой момент CPU/память упираются в лимиты;
    • как ведет себя база данных.

Такая связка позволяет заранее проверить, выдержит ли система планируемый рост нагрузки.


Заключение

Мониторинг производительности — это не один инструмент и не один график, а целая система измерений и наблюдаемости, которая:

  • помогает вам понимать реальное состояние приложения;
  • показывает, где находятся узкие места;
  • позволяет планировать развитие и масштабирование;
  • снижает время поиска и устранения проблем.

Вы видите, что в основе такого мониторинга лежат:

  • четкие цели (SLO и ключевые метрики);
  • правильное инструментирование кода;
  • выбранный стек (Prometheus, Grafana, OpenTelemetry и т.д.);
  • связка метрик, логов и трасс.

Если вы выстроите эту систему постепенно — от базовых метрик к распределенному трассированию и профилированию — у вас появится надежный фундамент для работы с производительностью, а оптимизации перестанут быть «угадайкой» и станут управляемым инженерным процессом.


Частозадаваемые технические вопросы

1. Как избежать слишком высокой кардинальности метрик в Prometheus

Не добавляйте в label значения, которые могут часто меняться или иметь много вариантов
Например, user_id, request_id, email
Используйте label’ы только с ограниченным набором значений
Если нужно анализировать по пользователям — отправляйте это в логи, а не в метрики
Регулярно просматривайте топ-метрик по количеству series и удаляйте лишние комбинации label’ов

2. Как мониторить производительность запросов к базе без изменения всего кода

Используйте обертки над драйвером БД или ORM с поддержкой hooks
Многие библиотеки позволяют перехватывать начало и конец запроса
В этих хуках измеряйте время и увеличивайте счетчики
Например, для PostgreSQL можно использовать proxy-слой PgBouncer или pgbadger для анализа логов и метрик без изменения приложения

3. Как связать логи и трассировки если стек уже частично реализован

Добавьте в код генерацию и проброс trace_id через контекст запроса
В логгер внедрите middleware который будет автоматом добавлять trace_id в каждую запись
В APM/Tracing системе настройте экспорт того же trace_id
Так вы сможете по одному идентификатору находить и трассу и набор логов по конкретному запросу

4. Как безопасно включать профилирование на продакшене

Давайте доступ к pprof или аналогам только по защищенному интерфейсу
Ограничьте его внутренней сетью или VPN
Включайте тяжелые профили (CPU, trace) на короткое время через отдельные команды или feature-флаги
Следите за overhead - если нагрузка большая, используйте выборочное профилирование на копии трафика или staging окружении

5. Что делать если метрики показывают норму а пользователи жалуются на медленную работу

Проверьте мониторинг с точки зрения пользователя - включите RUM или synthetic проверки
Убедитесь что метрики собираются во всех зонах и регионах а не только из одного датацентра
Проверьте сетевые задержки CDN DNS и работу фронтенда - возможно проблема в рендеринге или тяжелых ресурсах а не в backend API
Сопоставьте время по часам - иногда деградации происходят только при пиковой нагрузке или в определенные периоды дня

Оптимизация сборки в Go - build-optimizationСтрелочка вправо

Все гайды по Fsd

Открыть базу знаний

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