Как корректно обработать таймаут от базы данных, чтобы избежать ответа с кодом 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 временно прекращает отправку новых запросов, давая ей время на восстановление и предотвращая каскадные сбои в вашем сервисе.

Ответ 18+ 🔞

А, слушай, вот эта хуйня с таймаутами от базы данных — это ж классика, блядь! Приложение тупо падает в 500-ю ошибку, а клиент сидит, чешет репу: «Чё за пиздец?» Надо ж объяснить человеку, что база, сука, уснула, а не что сервер сгорел, ёпта!

1. Главный приём — 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. Дополнительные подпорки, чтоб совсем не облажаться

  • Правильный статус в ответ: Не вали всё в 500, как в помойку. 504 Gateway Timeout — это как сказать: «Я, блядь, ждал-ждал, да нихуя не дождался». Клиенту понятнее, мониторингу — приятнее.
  • Настрой пул соединений: А то откроют овердохуища соединений, БД захлебнётся, как алкаш в луже. SetMaxOpenConns и SetConnMaxLifetime — твои лучшие друзья, чтоб не плодить зомби-коннекты.
  • Повторные попытки (Retry): Иногда база просто чихнула — можно попробовать ещё разок, но не долбить её, как дятел. Сделай экспоненциальную задержку, чтоб не добить окончательно.
  • Предохранитель (Circuit Breaker): Если база совсем легла — прекращай слать запросы, дай ей отдышаться. А то все потоки в приложении повиснут в ожидании, и будет полный пиздец, в рот меня чих-пых!

Короче, суть в чём: не давай БД себя выебать. Контролируй время, ограничивай аппетиты, а если что — честно сообщай, кто именно обосрался.