Как подбирать тайм-ауты для backend взаимодействия, например HTTP запросов
Что проверяют
Знаете ли вы, что в Go по умолчанию тайм-аутов нет вообще, и умеете ли вывести число из метрик, а не взять «5 секунд, потому что так принято». Следом обычно спрашивают про повторы: сколько попыток помещается в бюджет и какие запросы вообще можно повторять.
Как отвечать
Сначала про умолчания. http.DefaultClient и http.Client{} без поля Timeout ждут ответа бесконечно, и зависший сервис копит у вас горутины и соединения. Серверный http.ListenAndServe тоже работает без тайм-аутов, это открытая дверь для slowloris.
Уровни, которые стоит назвать:
http.Client.Timeout: общий лимит на запрос целиком, включая редиректы и чтение тела ответа;http.Transport:net.Dialer{Timeout}вDialContext,TLSHandshakeTimeout,ResponseHeaderTimeout,IdleConnTimeout. Отделяют «не соединились» от «сервер думает»;- контекст:
http.NewRequestWithContextиcontext.WithTimeoutна конкретный вызов. Контекст входящего запроса передаётся вниз, и когда клиент ушёл, отменяются все дочерние запросы; - сервер:
ReadHeaderTimeout,ReadTimeout,WriteTimeout,IdleTimeoutуhttp.Server, для отдельных ручекhttp.TimeoutHandlerилиhttp.ResponseController.
Число берётся из распределения задержек зависимости: тайм-аут чуть выше её p99 или p99.9, а не среднего. Затем проверяется бюджет сверху вниз. Если у ручки SLA одна секунда и внутри два последовательных вызова, каждый не может ждать по секунде. Повторы с экспоненциальной задержкой и джиттером допустимы только для идемпотентных операций, и все попытки должны уложиться в оставшийся дедлайн контекста. Долгие операции лучше делать асинхронными, а не ставить тайм-аут в минуту.
Где спотыкаются
Используют http.Get или http.DefaultClient в продовом коде. Без явного Timeout проблема незаметна до первой деградации соседнего сервиса.
Ставят нижнему сервису тайм-аут больше, чем верхнему. Вызывающий уже вернул ошибку пользователю, а внизу запрос продолжает жечь ресурсы, и повторы добивают и так больной сервис.
Создают context.Background() внутри обработчика вместо r.Context(). Отмена от клиента никуда не доходит, и работа продолжается впустую.