Что делать при тайм-ауте запроса
Что проверяют
Различаете ли вы две разные ситуации за одним словом: нас не дождался клиент или мы не дождались чужой сервис. И знаете ли, где в Java-приложении таймаут вообще выставляется, потому что мест несколько и по умолчанию половина из них бесконечна.
Как отвечать
Начните с уточнения, чей это таймаут, и назовите слои, где он настраивается:
- HTTP-клиент: время на соединение и время на чтение ответа задаются отдельно, будь то
RestTemplateчерез фабрику,WebClientилиHttpClientиз JDK; - пул соединений к базе:
connectionTimeoutв HikariCP это ожидание свободного соединения, а не длительность запроса; - сам запрос и транзакция:
queryTimeoutна уровне JDBC,timeoutу@Transactional; - контейнер: сколько времени готов ждать тот, кто вызывает нас.
Ключевой принцип в том, что таймауты выстраиваются иерархией. Внутренний вызов обязан отвалиться раньше, чем истечёт терпение у вызывающего, иначе поток держится ради ответа, который уже никому не нужен.
Дальше диагностика: смотреть p95 и p99, а не среднее, найти по трассировке шаг, на котором уходит время, проверить очевидное, то есть исчерпание пула соединений, блокировки в базе, долгие паузы сборщика мусора. И только потом обвязка: повтор с экспоненциальной задержкой и джиттером для идемпотентных операций, предохранитель и ограничение одновременных вызовов через Resilience4j, деградация на кэш.
Где спотыкаются
Первым делом поднимают таймаут. Поток живёт дольше, пул потоков контейнера кончается быстрее, и вместо одной медленной ручки ложится весь сервис.
Повторяют что угодно. Повтор неидемпотентного POST после таймаута создаёт второй платёж или второй заказ: ответ не дошёл, но запрос вполне мог выполниться. Повторять можно то, что безопасно повторить, и с ограничением числа попыток.
Забывают, что у клиента по умолчанию таймаута может не быть вовсе. Кандидат рассказывает про повторы и предохранители, а в коде живёт клиент без ограничения на чтение, и первый же зависший бэкенд собирает на себе все потоки.