В продакшене async-генераторы часто воспринимаются как идеальный механизм для стриминга больших данных. Но при анализе p99 latency я обнаружил, что основной источник задержек — не медленные запросы к БД, а скрытые проблемы с утечками корутин и сборкой мусора.
Проблема 1: GC-latency при разрыве соединения
Когда клиент прерывает соединение, async-генератор продолжает висеть с циклическими ссылками. Поколенческий сборщик мусора начинает аварийные сборки, и latency может улетать за секунду.
async def stream_data():
for i in range(10**6):
yield await fetch_chunk(i)
Утечка: клиент ушёл, но генератор не завершён. Решение —
finally с aclose() или обёртка через @contextlib.asynccontextmanager. Правило: если ты не контролируешь время жизни генератора, контролируй очистку.Проблема 2: HWM (high water mark) и резервирование стека
Каждый async-генератор резервирует стек корутины при создании. В проде с тысячами одновременных запросов это даёт ощутимый overhead. Для профилирования используйте
gc.get_objects() с фильтром на AsyncGeneratorType:import gc, types
from collections import Counter
gen_count = Counter()
for obj in gc.get_objects():
if isinstance(obj, types.AsyncGeneratorType):
gen_count[type(obj).__name__] += 1
Рост счётчика — явный признак утечки. HWM можно оценить через
sys.getsizeof(), но лучше фокусироваться на количестве живых генераторов.FastAPI-specific паттерны утечек
На ревью часто вижу три типичные ошибки:
- Тайм-ауты: FastAPI отменяет задачу, но
aclose() не вызывается.- Циклические ссылки: генератор держит request-объект, GC в тупике.
- SSE-генераторы, висящие вечно, если клиент не закрыл соединение.
В продакшене включаю
PYTHONASYNCIODEBUG=1 для ловли Task was destroyed but it is pending. В тестовых средах — gc.set_debug(gc.DEBUG_LEAK). Для стриминга FastAPI использую шаблон с aclosing:from contextlib import aclosing
async def safe_stream():
async with aclosing(async_generator()):
async for item in async_generator():
yield item
Практический совет: всегда оборачивайте async-генераторы в контекстный менеджер с гарантированным вызовом
aclose(). Это снижает p99 latency на сотни миллисекунд.Вывод:
Один забытый async-генератор в high-load FastAPI-сервисе способен превратить стриминг в источник неконтролируемых задержек, поэтому профилирование GC и утечек корутин — обязательный шаг при оптимизации latency.