Как диагностировать зависание или узкое место в Go-сервисе?

Ответ

Для диагностики производительности и поиска причин зависания в Go-приложении используются встроенные и сторонние инструменты.

  1. Профилирование с помощью pprof Это стандартный и самый мощный инструмент для анализа производительности. Для его подключения достаточно добавить импорт и запустить HTTP-сервер:

    import (
        _ "net/http/pprof"
        "net/http"
        "log"
    )
    
    go func() {
        // pprof эндпоинты будут доступны на :6060/debug/pprof/
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()

    Основные эндпоинты для анализа:

    • go tool pprof http://.../debug/pprof/profile?seconds=30 — анализирует использование CPU.
    • go tool pprof http://.../debug/pprof/heap — анализирует распределение памяти.
    • http://.../debug/pprof/goroutine?debug=2 — показывает стектрейсы всех активных горутин, что критически важно для поиска зависаний.
  2. Контексты с таймаутами и дедлайнами Использование context.WithTimeout или context.WithDeadline позволяет прервать операцию (например, запрос к базе данных или другому сервису), если она выполняется слишком долго. Это предотвращает каскадные сбои.

    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    
    // Передаем контекст в функцию, которая выполняет долгую операцию
    err := longOperation(ctx)
    if errors.Is(err, context.DeadlineExceeded) {
        log.Println("Операция прервана по таймауту")
    }
  3. Распределенная трассировка В микросервисной архитектуре, когда запрос проходит через несколько сервисов, pprof не всегда помогает. Здесь нужны системы распределенной трассировки, такие как OpenTelemetry (стандарт де-факто) и Jaeger или Zipkin для визуализации. Они позволяют отследить весь путь запроса и найти, какой именно сервис или операция является узким местом.

  4. Метрики Сбор метрик с помощью библиотеки Prometheus и их визуализация в Grafana. Ключевые метрики для отслеживания:

    • Время ответа на запрос (latency), включая перцентили (p95, p99).
    • Количество активных горутин.
    • Использование памяти и CPU.

Краткий итог: для локальной диагностики одного сервиса начните с pprof (особенно с профиля горутин). Для контроля времени выполнения операций используйте context. В распределенных системах внедряйте трассировку и метрики.

Ответ 18+ 🔞

Да ты посмотри, что за пиздопроебибна тут происходит! Сидит программист, глаза в монитор уставил, а приложение висит, как будто его в рот мне чих-пых, и ни хуя не происходит. Ну, ладно, разбираемся, как эту жопу разгрести.

Первое дело, наш верный друг — pprof. Это такая встроенная штука, которая как рентген для твоего кода. Подключается проще простого, хуй с горы, вот так:

import (
    _ "net/http/pprof"
    "net/http"
    "log"
)

go func() {
    // pprof эндпоинты будут доступны на :6060/debug/pprof/
    log.Println(http.ListenAndServe("localhost:6060", nil))
}()

А дальше начинается магия. Запустил сервер, открываешь браузер и смотришь, где собака зарыта.

  • Хочешь понять, на каком процессоре твоя прога мозги выносит? go tool pprof http://.../debug/pprof/profile?seconds=30 — и вот тебе, блядь, полная картина, кто самый прожорливый.
  • Память жрёт, как не в себя? go tool pprof http://.../debug/pprof/heap — покажет, кто тут манда с ушами и аллоцирует память тоннами.
  • А самое главное, когда всё просто висит и молчит, как Герасим из того рассказа — тебе нужны горутины. Заходишь на http://.../debug/pprof/goroutine?debug=2 и видишь стектрейсы ВСЕХ этих мартышлюшек. Сразу видно, какая из них ушла в бесконечный цикл, а какая упёрлась в мьютекс и ждёт, пока её кто-нибудь разбудит. Удивление пиздец, когда находишь виновника!

Второй момент — контексты с таймаутами. Ну ёпта, представь, твой код пошёл в базу данных, а там что-то затупило. И он сидит, ждёт, как дурак, а все остальные запросы в очередь встают. Чтобы такого не было, делаем так:

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

// Передаем контекст в функцию, которая выполняет долгую операцию
err := longOperation(ctx)
if errors.Is(err, context.DeadlineExceeded) {
    log.Println("Операция прервана по таймауту")
}

Всё! Если операция за две секунды не управилась — ей кирдык. Не будет она терпения ебать у всей системы. Чистая профилактика от зависаний.

Ну а если у тебя там целый зоопарк из микросервисов, и непонятно, в каком из них запрос пропал — тут pprof уже не канает. Тут нужна распределённая трассировка, типа OpenTelemetry. Это как GPS-трекер для запроса. Он пробегает через все сервисы, и ты в Jaeger или Zipkin видишь весь его путь, и где он, сука, застрял на полчаса. Овердохуища полезная штука.

И конечно, метрики. Настроил Prometheus, накатал пару дашбордов в Grafana. Смотришь: время ответа выросло, количество горутин зашкаливает — сразу подозрение ебать чувствую, что-то не так. Превентивно лезешь смотреть, пока всё не накрылось медным тазом.

Короче, итог: Для одного сервиса — бей в pprof, особенно в профиль горутин. Для контроля времени — контексты рулят. Для сложных систем — трассировка и метрики, без них ты как слепой кот, сука собака. Вот и вся наука.