> Как определить причину медленной работы REST API при ответе через 20 секунд (Go)
Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: Лига Цифровой Экономики
Стек: Go
> Пример ответа
Короткий ответ
Причина медленного ответа REST API обычно кроется в одном из трёх уровней: сеть/инфраструктура, приложение или внешние зависимости. Начните с измерения времени на каждом уровне: используйте middleware для логирования длительности, проверьте время ответа upstream-сервисов, базы данных и кэша. В Go типичные причины - блокировки на mutex, утечки горутин, неэффективные запросы к БД или медленные HTTP-клиенты без таймаутов. Профилируйте с pprof и смотрите на trace.
Подробное объяснение
Медленный ответ за 20 секунд - это почти всегда проблема синхронного ожидания, а не вычислительной нагрузки. В Go это означает, что горутина заблокирована на чём-то: network I/O, канале, mutex или системном вызове.
Основные категории причин:
-
Инфраструктура: DNS-резолвинг, TLS handshake, прокси/балансировщик, медленный клиент. Проверьте curl с
-wдля разбивки времени, посмотрите на keep-alive. -
Приложение:
- Блокировки:
sync.Mutex,sync.RWMutex, каналы без буфера - Утечки горутин: горутина ждёт на канале, который никто не закроет
- Неправильный пул соединений: HTTP client без
MaxIdleConnsPerHost, БД безSetMaxOpenConns - Сериализация: большие JSON без streaming,
json.Marshalна огромных структурах
- Блокировки:
-
Внешние зависимости:
- БД: медленный запрос, блокировки строк, отсутствие индекса
- Кэш: Redis с долгим RTT, cache miss на каждый запрос
- Другие сервисы: цепочка вызовов, где каждый добавляет задержку
-
Go-специфика:
http.ServerбезReadTimeout/WriteTimeout- медленный клиент держит горутину- GC pressure при большом аллоцировании
runtime.Goschedили busy loop в критическом месте
Диагностика идёт от общего к частному: сначала внешние замеры, потом middleware, потом pprof, потом trace.
На практике
Порядок действий при жалобе на 20-секундный ответ:
-
Воспроизведите с
curl -w "@format.txt"- получите разбивку:time_total,time_connect,time_starttransfer. -
Добавьте middleware, который логирует
time.Since(start)и статус. Сравните с ожидаемым. -
Если время уходит до обработчика - проблема в инфраструктуре или в
http.Server(например,ReadHeaderTimeout). -
Если время внутри обработчика - смотрите на внешние вызовы. Добавьте таймауты на каждый HTTP-клиент и запрос к БД.
-
Запустите
pprof:go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30- Смотрите на
top- где больше всего времени go tool pprof http://localhost:6060/debug/pprof/goroutine- проверьте, нет ли заблокированных горутин
-
Используйте
go tool traceдля событий network I/O и блокировок. -
Проверьте пулы:
runtime.NumGoroutine()- если растёт, есть утечка.
Пример кода
Middleware для замера времени:
GOfunc 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-клиент с правильными таймаутами:
GOclient := &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,},}
Проверка на утечку горутин:
GOfunc 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)
> Похожие задачи по Go
В каком формате передаются данные по сети
Когда завершится цикл for при чтении из канала в Go
Какие инструменты кроме трассировки используются для диагностики производительности
Как избежать утечек памяти при работе с файлами в цикле в Go
> Похожие задачи по backend
В каком формате передаются данные по сети
Когда завершится цикл for при чтении из канала в Go
Какие инструменты кроме трассировки используются для диагностики производительности
Как избежать утечек памяти при работе с файлами в цикле в Go
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью