Почему асинхронный сервис может деградировать и переставать отвечать на запросы
Что проверяют
Понимаете ли вы, что в asyncio нет вытеснения: событийный цикл один, и любая корутина держит его до ближайшего await. Вопрос практический, его задают тем, кто уже ловил рост времени ответа без роста нагрузки на процессор.
Как отвечать
Перечислять причины по механизму, а не списком страшных слов.
- Блокировка цикла. Синхронный вызов внутри корутины (
requests,time.sleep, драйвер базы без асинхронной обёртки, разбор огромного JSON, хеширование пароля) останавливает все задачи разом. Диагностика: режим отладки цикла и порогslow_callback_duration, метрика задержки цикла. - Нет ограничения конкурентности. Сервис принимает запросы быстрее, чем закрывает, число задач растёт вместе с памятью. Клиент давно отвалился по таймауту, а его задача всё ещё выполняется и занимает ресурсы.
- Нет таймаутов на исходящие вызовы. Медленный сосед держит корутины открытыми, пул соединений исчерпан, и его деградация становится вашей.
- Утечки: незакрытые клиентские сессии, задачи без сохранённой ссылки (сборщик мусора вправе убрать такую задачу прямо посреди работы), очереди без верхней границы.
Лечится симметрично: блокирующее выносится в пул потоков через run_in_executor или в отдельный процесс, конкурентность ограничивается семафором, на каждый внешний вызов вешается asyncio.timeout, очереди делаются ограниченными, чтобы отказ был быстрым и явным. Хорошо добавить, что счётную нагрузку асинхронность не лечит вовсе: мешает GIL, помогает только отдельный процесс.
Где спотыкаются
Называют причиной «слишком много операций ввода-вывода». Как раз они асинхронному сервису не вредят, на них он и построен. Вредит синхронный вызов, притворяющийся асинхронным.
Второе: предлагают лечить перегрузку пулом потоков. Пул спасает от блокирующего вызова, но не от неограниченной очереди: задачи всё так же копятся, просто в другом месте.
Третье: не отличают «медленно» от «не отвечает». Если цикл заблокирован, перестают отвечать и проба готовности, и отдача метрик, оркестратор перезапускает контейнер, и в логах остаётся только рестарт без причины.