> Зачем добавлять контекст в запросы к базе данных? (Go)

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

Компании: InDrive

Стек: Go

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

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

Контекст в запросах к базе данных нужен для управления жизненным циклом операции: отмены, таймаутов и передачи метаданных (например, trace ID). В Go context.Context - стандартный механизм, который позволяет корректно завершать долгие запросы, освобождать ресурсы и избегать утечек горутин. Без контекста запрос будет выполняться до победного конца, даже если клиент уже отключился, что приводит к деградации сервиса.

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

Основная задача контекста - распространение сигналов отмены и дедлайнов через границы вызовов. В контексте БД это означает:

  • Отмена запроса - если клиент разорвал соединение или сервис завершает работу, запрос к БД должен быть прерван, а не продолжать выполняться в фоне.
  • Таймауты - контекст позволяет задать максимальное время выполнения запроса. Это защищает от зависаний, когда БД не отвечает или запрос слишком тяжёлый.
  • Проброс метаданных - через контекст можно передавать идентификаторы трассировки, заголовки аутентификации и другие данные, которые нужны на уровне драйвера БД или middleware.

В Go драйверы БД (например, database/sql, pgx, mongo-go-driver) принимают context.Context первым аргументом. Если передать context.Background() или context.TODO(), операция будет выполняться без ограничений по времени и без возможности отмены.

Важно понимать, что контекст - это не только про БД. Это общий паттерн для всех I/O операций: HTTP-запросов, файловых операций, очередей. В контексте БД он особенно критичен, потому что запросы могут быть долгими и ресурсоёмкими.

На практике

В реальном коде контекст обычно создаётся на границе запроса (например, в HTTP handler) и пробрасывается вниз по стеку вызовов. Для БД это выглядит так:

  • В handler создаётся контекст с таймаутом через context.WithTimeout.
  • Контекст передаётся в сервисный слой, затем в репозиторий, и наконец в метод драйвера БД.
  • Если операция занимает больше времени, чем таймаут, контекст отменяется, и запрос к БД прерывается.

Также стоит учитывать, что контекст нужно передавать явно, а не хранить в структурах. Это требование из документации Go: context.Context должен быть первым параметром функции, а не полем структуры.

Пример кода

GO
func GetUser(ctx context.Context, userID int) (*User, error) {
// Создаём контекст с таймаутом 2 секунды
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
var user User
err := db.QueryRowContext(ctx,
"SELECT id, name FROM users WHERE id = $1", userID,
).Scan(&user.ID, &user.Name)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, fmt.Errorf("query timeout: %w", err)
}
return nil, err
}
return &user, nil
}

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

Начните с главного: контекст нужен для управления временем жизни запроса и его отмены. Затем перечислите конкретные сценарии: таймауты, отмена при отключении клиента, проброс метаданных. Упомяните, что в Go это стандартный механизм, и драйверы БД его поддерживают.

Если спросят про context.Background() - объясните, что это допустимо только для операций, которые не должны иметь ограничений (например, миграции), но в production-коде лучше всегда передавать контекст от вышестоящего вызова.

Хорошо показать понимание иерархии: контекст создаётся один раз на границе запроса и пробрасывается через все слои. Также можно упомянуть, что отмена контекста освобождает соединение из пула - это важный момент для производительности.

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

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

  • Зачем нужен контекст в принципе (не только для БД).
  • Как контекст связан с управлением ресурсами и пулом соединений.
  • Почему нельзя игнорировать контекст или использовать context.Background() без причины.
  • Как правильно создавать и отменять контекст (через defer cancel()).
  • Разницу между context.WithTimeout, context.WithCancel и context.WithDeadline.

Также проверяется понимание того, что контекст - это не просто параметр, а часть контракта функции. Если функция принимает контекст, она обязана его использовать.

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

  • Использование context.Background() вместо передачи контекста извне - приводит к тому, что запрос нельзя отменить или ограничить по времени.
  • Забывание defer cancel() - утечка ресурсов, контекст не будет отменён после завершения функции.
  • Хранение контекста в структуре - нарушение рекомендаций Go, контекст должен передаваться явно.
  • Игнорирование ошибки отмены - если контекст отменён, запрос вернёт ошибку, и её нужно обрабатывать, а не молча игнорировать.
  • Создание контекста на каждый запрос внутри функции - если контекст уже передан, не нужно создавать новый, лучше использовать context.WithTimeout от родительского.
  • Передача nil вместо контекста - это паника в большинстве драйверов, всегда передавайте хотя бы context.Background().

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

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