Как корректно обработать таймаут от базы данных, чтобы избежать ответа с кодом 500?

Ответ

Таймаут от БД — это ожидаемая ошибка в распределенных системах. Если ее не обработать, приложение вернет клиенту общую ошибку 500 Internal Server Error, что неинформативно. Правильная обработка включает несколько уровней защиты.

1. Использование context с таймаутом (Основное решение)

Для каждого входящего запроса создавайте context с дедлайном и передавайте его во все вызовы, взаимодействующие с внешними системами (БД, другие сервисы).

func handleRequest(w http.ResponseWriter, r *http.Request) {
    // Устанавливаем таймаут на операцию с БД, например, 3 секунды
    ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
    defer cancel()

    var user User
    // Передаем контекст в вызов к БД
    err := db.QueryRowContext(ctx, "SELECT name FROM users WHERE id = $1", 1).Scan(&user.Name)
    if err != nil {
        // Проверяем, была ли ошибка вызвана именно таймаутом контекста
        if errors.Is(err, context.DeadlineExceeded) {
            // Возвращаем клиенту корректный код ошибки
            http.Error(w, "Database operation timed out", http.StatusGatewayTimeout) // 504
            return
        }
        // Обрабатываем другие возможные ошибки БД
        http.Error(w, "Internal Server Error", http.StatusInternalServerError) // 500
        return
    }
    // ... успешный ответ
}

2. Дополнительные защитные меры

  • Правильный HTTP-статус: Вместо 500 Internal Server Error лучше использовать 504 Gateway Timeout или 503 Service Unavailable. Это точнее описывает проблему для клиента и систем мониторинга.
  • Настройка пула соединений: Ограничьте максимальное количество открытых соединений (SetMaxOpenConns) и время жизни соединения (SetConnMaxLifetime), чтобы предотвратить исчерпание ресурсов и использование "зависших" соединений.
  • Логика повторных запросов (Retry): Для временных ошибок можно реализовать механизм повторных запросов, желательно с экспоненциальной задержкой (exponential backoff), чтобы не "завалить" запросами и без того нагруженную БД.
  • Паттерн Circuit Breaker (Предохранитель): Если БД стабильно не отвечает, Circuit Breaker временно прекращает отправку новых запросов, давая ей время на восстановление и предотвращая каскадные сбои в вашем сервисе.