Это не микрооптимизация, а механизм выживания backend-сервиса под нагрузкой. В production проблема часто появляется на медленном downstream: таски плодятся, память растет, клиенты ретраят, и падает уже цепочка сервисов.
Очередь не должна быть бесконечной
asyncio.Queue() без maxsize часто превращает память процесса в скрытый буфер аварии. Делайте очередь bounded и решайте, что делать при переполнении: ждать, вернуть 429/503 или отбросить низкоприоритетную работу.Лимитируйте конкуренцию
Async не означает “можно запустить 100k запросов к API или базе”.
queue = asyncio.Queue(maxsize=1000)
limit = asyncio.Semaphore(50)
async def submit(item):
try:
queue.put_nowait(item)
except asyncio.QueueFull:
raise Overloaded()
async def worker():
while True:
item = await queue.get()
try:
async with limit:
await asyncio.wait_for(
process(item),
timeout=2.0,
)
finally:
queue.task_done()
Здесь
Queue(maxsize=1000) ограничивает память, Semaphore(50) защищает downstream, а timeout не дает зависшим операциям держать слоты навсегда.Типичная ошибка
Плохая стратегия - принять все, сложить в память и надеяться “потом разгребем”. Под нагрузкой надежнее явно деградировать: 429/503, bounded wait, circuit breaker, durable queue для допустимых сценариев.
Что измерять
Минимум: размер очереди, время ожидания, rejected/dropped, saturation семафоров и пулов, timeout rate, latency downstream и retry rate. Без этих метрик backpressure превращается в догадку.
Вывод:
Надежный async-сервис ограничен по памяти, конкуренции и времени ожидания, иначе он становится усилителем cascading failure.
