5.3 KiB
5.3 KiB
[DEF:ADR-0011:ADR]
@STATUS ACTIVE
@PURPOSE Документировать архитектурное решение о полном переходе backend на async-first архитектуру: замена синхронного requests.Session на httpx.AsyncClient, введение глобальных per‑env семафоров, bounded executors и полный переход Task Manager на asyncio.
@RATIONALE ss-tools одновременно взаимодействует с несколькими внешними системами (Superset REST API, LLM API, SMTP, Telegram, Slack) и выполняет блокирующие операции (Git, файловый I/O, SQLAlchemy). При одном воркере uvicorn синхронные блокирующие вызовы останавливают все входящие HTTP-запросы. Единственный способ обеспечить конкурентность — сделать весь I/O неблокирующим.
@REJECTED aiohttp — отвергнут в пользу httpx, который уже был в зависимостях (0.28.1) и имеет лучший API для async HTTP.
@REJECTED Полный переход на async SQLAlchemy — потребовал бы переписывания всех моделей и репозиториев, что выходит за рамки этой фичи. run_blocking с bounded executor достаточен.
@REJECTED Dual-stack (sync + async) — потребовал бы поддержки двух транспортов, увеличивая surface для ошибок и усложняя код.
@REJECTED Wrapper-only (asyncio.to_thread на всё) — rejected, т.к. asyncio.to_thread не принимает executor и использует default пул без backpressure.
@REJECTED Multiprocessing / --workers N — rejected, т.к. увеличение воркеров не решает проблему блокировки внутри одного воркера и увеличивает потребление памяти.
Decision
Весь backend переводится на async-first архитектуру:
Transport Layer
- HTTP-клиент:
requests.Session→httpx.AsyncClientчерез классAsyncAPIClientвcore/utils/async_network.py. - SupersetClient: sync + AsyncSupersetClient объединены в единый асинхронный клиент. Все 13 миксинов —
async def. - Семафоры: Глобальный per‑env
asyncio.Semaphoreчерез синглтонSupersetClientRegistry(core/utils/client_registry.py). При исчерпании — таймаут 30s → 503. - LLM-клиент: Унифицированный
LLMHttpClientвplugins/translate/_llm_async_http.pyнаhttpx.AsyncClient. Rate-limit backoff черезasyncio.sleep.
Blocking Operations
- Bounded executors: Именованные
ThreadPoolExecutor(db, file, git) через helperrun_blocking(kind, fn)вcore/utils/executors.py. Вместоasyncio.to_thread. - Graceful shutdown: Все клиенты закрываются при shutdown через
SupersetClientRegistry.shutdown(). Незавершённые задачи помечаются CANCELLED.
Task Manager
- Запуск задач:
ThreadPoolExecutor→asyncio.create_task. Задачи разделяют основной event loop. - EventBus:
queue.Queue+call_soon_threadsafe→asyncio.Queue(maxsize=10000)с fan‑out на подписчиков. - Lifecycle: Thread-based → async context manager.
Конфигурация
- Per‑env:
connection_pool_size,connection_pool_timeout,request_timeout,llm_timeoutвEnvironmentConfig. - App‑level:
db_executor_workers,file_executor_workers,git_executor_workers,graceful_shutdown_timeoutвAppAsyncRuntimeConfig.
Тестирование
requests_mock→pytest-httpxдля мокирования асинхронных HTTP-вызовов.- Тесты конкурентности с
asyncio.gatherдля всех C3+ контрактов. - Rejected-path regression тесты для каждого запрещённого паттерна.
Модули, затронутые рефакторингом
core/utils/:async_network.py(+semaphore),client_registry.py(new),executors.py(new),fileio.py(+async wrappers),network.py→ Tombstone,superset_compilation_adapter.py→ asynccore/superset_client/:_base.py(+13 mixins) → все asynccore/task_manager/:manager.py,event_bus.py,lifecycle.py→ все asyncapi/routes/: dashboards, assistant, migration, datasets, git, environments, profile → все asyncplugins/translate/:_llm_async_http.py(new), все файлы → asyncplugins/:backup.py,git_plugin.py,llm_analysis/plugin.py,storage/plugin.py→ asyncservices/:notifications/providers.py,notifications/service.py,git/_base.py→ asyncservices/:git/_base.py,git/_branch.py,git/_sync.py,git/_status.py,git/_merge.py→ async