> Как определить причину медленной работы REST API при ответе через 20 секунд (Go)

Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: Лига Цифровой Экономики

Стек: Go

> Пример ответа

Короткий ответ

Причина медленного ответа REST API обычно кроется в одном из трёх уровней: сеть/инфраструктура, приложение или внешние зависимости. Начните с измерения времени на каждом уровне: используйте middleware для логирования длительности, проверьте время ответа upstream-сервисов, базы данных и кэша. В Go типичные причины - блокировки на mutex, утечки горутин, неэффективные запросы к БД или медленные HTTP-клиенты без таймаутов. Профилируйте с pprof и смотрите на trace.

Подробное объяснение

Медленный ответ за 20 секунд - это почти всегда проблема синхронного ожидания, а не вычислительной нагрузки. В Go это означает, что горутина заблокирована на чём-то: network I/O, канале, mutex или системном вызове.

Основные категории причин:

  1. Инфраструктура: DNS-резолвинг, TLS handshake, прокси/балансировщик, медленный клиент. Проверьте curl с -w для разбивки времени, посмотрите на keep-alive.

  2. Приложение:

    • Блокировки: sync.Mutex, sync.RWMutex, каналы без буфера
    • Утечки горутин: горутина ждёт на канале, который никто не закроет
    • Неправильный пул соединений: HTTP client без MaxIdleConnsPerHost, БД без SetMaxOpenConns
    • Сериализация: большие JSON без streaming, json.Marshal на огромных структурах
  3. Внешние зависимости:

    • БД: медленный запрос, блокировки строк, отсутствие индекса
    • Кэш: Redis с долгим RTT, cache miss на каждый запрос
    • Другие сервисы: цепочка вызовов, где каждый добавляет задержку
  4. Go-специфика:

    • http.Server без ReadTimeout/WriteTimeout - медленный клиент держит горутину
    • GC pressure при большом аллоцировании
    • runtime.Gosched или busy loop в критическом месте

Диагностика идёт от общего к частному: сначала внешние замеры, потом middleware, потом pprof, потом trace.

На практике

Порядок действий при жалобе на 20-секундный ответ:

  1. Воспроизведите с curl -w "@format.txt" - получите разбивку: time_total, time_connect, time_starttransfer.

  2. Добавьте middleware, который логирует time.Since(start) и статус. Сравните с ожидаемым.

  3. Если время уходит до обработчика - проблема в инфраструктуре или в http.Server (например, ReadHeaderTimeout).

  4. Если время внутри обработчика - смотрите на внешние вызовы. Добавьте таймауты на каждый HTTP-клиент и запрос к БД.

  5. Запустите pprof:

    • go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
    • Смотрите на top - где больше всего времени
    • go tool pprof http://localhost:6060/debug/pprof/goroutine - проверьте, нет ли заблокированных горутин
  6. Используйте go tool trace для событий network I/O и блокировок.

  7. Проверьте пулы: runtime.NumGoroutine() - если растёт, есть утечка.

Пример кода

Middleware для замера времени:

GO
func TimingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s took %v", r.Method, r.URL.Path, time.Since(start))
})
}

HTTP-клиент с правильными таймаутами:

GO
client := &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
DialContext: (&net.Dialer{
Timeout: 2 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 2 * time.Second,
},
}

Проверка на утечку горутин:

GO
func CheckGoroutines() {
go func() {
for {
time.Sleep(10 * time.Second)
log.Printf("goroutines: %d", runtime.NumGoroutine())
}
}()
}

Как отвечать на собеседовании

Начните с чёткого плана: "Сначала я бы измерил, где именно теряется время - на уровне сети, приложения или БД". Покажите, что знаете инструменты: curl -w, middleware, pprof, trace.

Упомяните, что 20 секунд - это почти всегда ожидание, а не вычисления. В Go это особенно важно из-за модели concurrency.

Расскажите про конкретные причины в Go: блокировки на mutex, каналы, утечки горутин, неправильные таймауты. Приведите пример, как http.Client без Timeout может держать горутину вечно.

Покажите системный подход: не гадать, а измерять. Сначала внешние замеры, потом middleware, потом профилирование.

Что проверяет интервьюер

  • Умение системно подходить к диагностике, а не гадать
  • Знание Go-специфики: горутины, каналы, mutex, pprof
  • Понимание сетевого стека: DNS, TLS, keep-alive, таймауты
  • Умение работать с инструментами: curl, pprof, trace
  • Способность отличать проблемы инфраструктуры от проблем приложения
  • Знание паттернов: middleware, пулы соединений, таймауты

Типичные ошибки

  • Начинать с чтения кода вместо измерений
  • Игнорировать сетевой уровень - сразу лезть в код
  • Не проверять утечки горутин
  • Забывать про таймауты на HTTP-клиентах и запросах к БД
  • Не использовать pprof, а пытаться угадать причину
  • Путать время ответа с временем обработки - не учитывать время на передачу данных клиенту
  • Не проверять runtime.NumGoroutine() при подозрении на утечку
  • Считать, что проблема в коде, когда она в конфигурации сервера (ReadTimeout, WriteTimeout)

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью