> Как определить, что запрос в базе тормозит (Go)

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

Компании: Employcity

Стек: Go, Java

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

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

Определить тормозящий запрос можно через observability: метрики latency и throughput на уровне SQL, логи медленных запросов, трассировку и профилирование. В Go и Java смотрят на context deadline, пул соединений и время ожидания в очереди. Дальше подтверждают гипотезу через EXPLAIN ANALYZE, pg_stat_statements или аналоги и смотрят на планы выполнения.

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

На уровне инфраструктуры тормоза проявляются как рост p95 и p99 latency, падение throughput, увеличение времени ожидания соединения из пула и таймауты на клиенте. Важно разделять время в самой базе и время до неё: сеть, DNS, TLS, балансировщик, пул соединений. Внутри базы смотрят на slow query log, pg_stat_statements, performance_schema, wait events и блокировки. Отдельно проверяют план выполнения: seq scan на большой таблице, неверная оценка кардинальности, nested loop на миллионах строк, сортировка на диске, spill в temp files. На уровне приложения тормоза часто вызваны N+1, отсутствием индексов, отсутствием батчинга, слишком большими транзакциями и удержанием соединения во время внешних вызовов. В Go это видно через context, pprof и метрики database/sql, в Java - через Micrometer, HikariCP metrics и JFR.

На практике

Сначала смотрят на дашборды: latency по endpoint, latency по запросу, ошибки, таймауты, размер пула. Потом идут в slow query log и pg_stat_statements, сортируют по total time и mean time. Затем берут конкретный запрос и делают EXPLAIN (ANALYZE, BUFFERS). Проверяют индексы, статистику, блокировки, wait events. В приложении проверяют, не держится ли соединение во время HTTP-вызова, нет ли N+1, не слишком ли большой batch. В Go смотрят на context deadline и на то, не отменяется ли запрос раньше времени. В Java смотрят на HikariCP: active, idle, pending, timeout. Если запрос тормозит только под нагрузкой, проверяют contention, cache miss, autovacuum, checkpoint и репликацию.

Пример кода

GO
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel()
start := time.Now()
rows, err := db.QueryContext(ctx, `
SELECT id, email
FROM users
WHERE created_at >= $1
ORDER BY created_at DESC
LIMIT 100
`, since)
if err != nil {
log.Printf("query failed after %s: %v", time.Since(start), err)
return err
}
defer rows.Close()
log.Printf("query took %s", time.Since(start))
JAVA
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(
"SELECT id, email FROM users WHERE created_at >= ? ORDER BY created_at DESC LIMIT 100")) {
ps.setQueryTimeout(1);
long start = System.nanoTime();
try (ResultSet rs = ps.executeQuery()) {
// process
}
long elapsedMs = (System.nanoTime() - start) / 1_000_000;
log.info("query took {} ms", elapsedMs);
}

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

Сначала назовите уровни диагностики: метрики, логи, трассировка, профилирование, план запроса. Потом объясните, как отделить проблему базы от проблемы приложения и сети. Упомяните pg_stat_statements, slow query log, EXPLAIN ANALYZE, wait events, блокировки. Скажите про пул соединений и context deadline в Go, про HikariCP и JFR в Java. Покажите, что умеете идти от симптома к причине: p99 latency, таймауты, N+1, seq scan, spill, lock contention. В конце добавьте, как проверяете гипотезу и как измеряете улучшение.

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

Проверяет системное мышление: умеет ли кандидат разделять слои и не путать причину со следствием. Проверяет знание инструментов observability и профилирования. Проверяет понимание планов выполнения, индексов, статистики и блокировок. Проверяет навык работы с пулом соединений и таймаутами в Go и Java. Проверяет умение формулировать гипотезы и подтверждать их данными. Проверяет опыт диагностики под нагрузкой и понимание trade-off между latency и throughput.

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

Смотреть только на среднее время и игнорировать p95 и p99. Путать время в базе со временем на клиенте. Делать выводы без EXPLAIN ANALYZE. Добавлять индекс без проверки плана и статистики. Держать соединение во время внешнего вызова. Игнорировать пул соединений и его очередь. Не учитывать блокировки и wait events. Забывать про N+1 и батчинг. Оптимизировать запрос, который на самом деле не является узким местом.

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

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