Files
ss-tools/docs/adr/ADR-0011-async-backend.md
busya ec6421de35 rename ss-tools to superset-tools across the entire project
- Replace all occurrences of 'ss-tools' with 'superset-tools' in 104 files
- Rename git bundle file ss-tools.bundle → superset-tools.bundle
- Update .gitignore pattern accordingly
- Preserve variable names (hasSsTools etc.) and code identifiers
2026-06-16 11:15:19 +03:00

56 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# [DEF:ADR-0011:ADR]
# @STATUS ACTIVE
# @PURPOSE Документировать архитектурное решение о полном переходе backend на async-first архитектуру: замена синхронного `requests.Session` на `httpx.AsyncClient`, введение глобальных perenv семафоров, bounded executors и полный переход Task Manager на asyncio.
# @RATIONALE superset-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. **Семафоры**: Глобальный perenv `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)` с fanout на подписчиков.
9. **Lifecycle**: Thread-based → async context manager.
### Конфигурация
10. **Perenv**: `connection_pool_size`, `connection_pool_timeout`, `request_timeout`, `llm_timeout` в `EnvironmentConfig`.
11. **Applevel**: `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]