Какие варианты предложить для исключения проседания производительности из-за медленного стороннего сервиса
Что проверяют
Понимаете ли вы, что медленная зависимость опасна тем, что забирает ваши воркеры и роняет весь сервис. Интервьюер ждёт набор защитных мер, а следом спрашивает: «Что увидит пользователь, если сервис вообще не отвечает?»
Как отвечать
Начните с механики. У Gunicorn с четырьмя синхронными воркерами и внешним вызовом на 10 секунд пропускная способность падает до 0,4 запроса в секунду, и остальные эндпоинты встают в очередь. Меры:
- таймауты: у
requestsтаймаута по умолчанию нет, поэтомуtimeout=(connect, read)обязателен, уhttpxпо умолчанию 5 секунд. Значение берут от реальных перцентилей сервиса; - повторы с экспоненциальной задержкой и jitter:
tenacity, только для идемпотентных операций и с лимитом попыток; - circuit breaker: после серии ошибок вызовы на время прекращаются и сразу отдают запасной ответ;
- изоляция (bulkhead): лимит одновременных вызовов через
asyncio.Semaphoreили отдельный пул, чтобы зависимость не заняла все воркеры; - кэш и деградация: ответ лежит в Redis, при сбое отдаётся последняя известная версия;
- вынос из запроса: вызов уходит в Celery, клиент получает
202 Acceptedи узнаёт результат опросом или вебхуком; - async: в FastAPI берут
httpx.AsyncClientс общим пулом соединений; - батчинг: объединять запросы и обновлять справочные данные по расписанию, а не на каждый запрос.
Замыкает ответ мониторинг: p95, p99 и доля ошибок по каждой внешней зависимости, алерт при выходе за SLA поставщика.
Где спотыкаются
Предлагают только кэш. Он не спасает при первом запросе и для персональных данных, а без таймаутов первая же деградация положит приложение.
Ставят повторы без общего бюджета времени: три попытки по 30 секунд превращают медленный ответ в ожидание на полторы минуты.
Переходят на async, оставив внутри requests. Код выглядит асинхронным, но синхронный вызов в async def блокирует event loop, и встают все запросы процесса.