# [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 1. **HTTP-клиент**: `requests.Session` → `httpx.AsyncClient` через класс `AsyncAPIClient` в `core/utils/async_network.py`. 2. **SupersetClient**: sync + AsyncSupersetClient объединены в единый асинхронный клиент. Все 13 миксинов — `async def`. 3. **Семафоры**: Глобальный per‑env `asyncio.Semaphore` через синглтон `SupersetClientRegistry` (`core/utils/client_registry.py`). При исчерпании — таймаут 30s → 503. 4. **LLM-клиент**: Унифицированный `LLMHttpClient` в `plugins/translate/_llm_async_http.py` на `httpx.AsyncClient`. Rate-limit backoff через `asyncio.sleep`. ### Blocking Operations 5. **Bounded executors**: Именованные `ThreadPoolExecutor` (db, file, git) через helper `run_blocking(kind, fn)` в `core/utils/executors.py`. Вместо `asyncio.to_thread`. 6. **Graceful shutdown**: Все клиенты закрываются при shutdown через `SupersetClientRegistry.shutdown()`. Незавершённые задачи помечаются CANCELLED. ### Task Manager 7. **Запуск задач**: `ThreadPoolExecutor` → `asyncio.create_task`. Задачи разделяют основной event loop. 8. **EventBus**: `queue.Queue` + `call_soon_threadsafe` → `asyncio.Queue(maxsize=10000)` с fan‑out на подписчиков. 9. **Lifecycle**: Thread-based → async context manager. ### Конфигурация 10. **Per‑env**: `connection_pool_size`, `connection_pool_timeout`, `request_timeout`, `llm_timeout` в `EnvironmentConfig`. 11. **App‑level**: `db_executor_workers`, `file_executor_workers`, `git_executor_workers`, `graceful_shutdown_timeout` в `AppAsyncRuntimeConfig`. ### Тестирование 12. `requests_mock` → `pytest-httpx` для мокирования асинхронных HTTP-вызовов. 13. Тесты конкурентности с `asyncio.gather` для всех C3+ контрактов. 14. 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` → async - `core/superset_client/`: `_base.py` (+13 mixins) → все async - `core/task_manager/`: `manager.py`, `event_bus.py`, `lifecycle.py` → все async - `api/routes/`: dashboards, assistant, migration, datasets, git, environments, profile → все async - `plugins/translate/`: `_llm_async_http.py` (new), все файлы → async - `plugins/`: `backup.py`, `git_plugin.py`, `llm_analysis/plugin.py`, `storage/plugin.py` → async - `services/`: `notifications/providers.py`, `notifications/service.py`, `git/_base.py` → async - `services/`: `git/_base.py`, `git/_branch.py`, `git/_sync.py`, `git/_status.py`, `git/_merge.py` → async # [/DEF:ADR-0011:ADR]