169 lines
7.8 KiB
Markdown
169 lines
7.8 KiB
Markdown
# Data Model: Async Backend Refactoring
|
||
|
||
**Feature**: `032-translate-requests-httpx`
|
||
**Date**: 2026-06-04
|
||
|
||
## Overview
|
||
|
||
Рефакторинг **не добавляет новых бизнес-таблиц** (SQLAlchemy models) и **не меняет публичные request/response Pydantic-схемы**. Добавляются транспортные/runtime-сущности (`SupersetClientRegistry`, `run_blocking`, bounded executors) и расширяется конфигурация окружений (`EnvironmentConfig`) и приложения (`AppAsyncRuntimeConfig`). Требование миграции БД зависит от формата хранения `EnvironmentConfig`: если JSON/Pydantic blob — defaults через `Field(default=...)` достаточны; если SQL‑колонки — нужна Alembic-миграция.
|
||
|
||
## 1. Changed Entities (Transport Layer)
|
||
|
||
### AsyncAPIClient (replaces APIClient)
|
||
|
||
| Attribute | Type | Change |
|
||
|-----------|------|--------|
|
||
| `session` | `requests.Session` → `httpx.AsyncClient` | Замена sync клиента на async |
|
||
| `base_url` | `str` | Без изменений |
|
||
| `csrf_token` | `str` | Без изменений (кешируется) |
|
||
| `semaphore` | **NEW** `asyncio.Semaphore` | Ограничение конкурентных соединений |
|
||
| `timeout` | **NEW** `httpx.Timeout` | Настраиваемый таймаут (default: 30s) |
|
||
|
||
**Lifecycle**: Клиент создаётся через `async with AsyncAPIClient(env_config) as client:` — контекстный менеджер управляет жизненным циклом `httpx.AsyncClient` (открытие/закрытие connection pool).
|
||
|
||
### SupersetClient (merged sync + async)
|
||
|
||
| Attribute | Type | Change |
|
||
|-----------|------|--------|
|
||
| `network` | `APIClient` → `AsyncAPIClient` | Замена sync на async |
|
||
| All mixin methods | `def` → `async def` | Все 13 миксинов становятся async |
|
||
|
||
**Methods (typical signature change)**:
|
||
```python
|
||
# Before (sync)
|
||
def get_dashboards(self, query=None) -> tuple[int, list[dict]]: ...
|
||
|
||
# After (async)
|
||
async def get_dashboards(self, query=None) -> tuple[int, list[dict]]: ...
|
||
```
|
||
|
||
### SupersetSqlLabExecutor
|
||
|
||
| Attribute | Type | Change |
|
||
|-----------|------|--------|
|
||
| `_client` | `SupersetClient` (sync) | Замена на async |
|
||
| `execute_sql()` | `def` → `async def` | Асинхронный HTTP + поллинг |
|
||
| `poll_execution_status()` | `def` → `async def` | `time.sleep` → `asyncio.sleep` |
|
||
| `max_polls` | `int` (60) | Без изменений |
|
||
| `poll_interval_seconds` | `float` (2.0) | Без изменений |
|
||
|
||
### TaskManager
|
||
|
||
| Attribute | Type | Change |
|
||
|-----------|------|--------|
|
||
| `executor` | `ThreadPoolExecutor` | **Удалён** |
|
||
| `_tasks` | `dict[str, asyncio.Task]` | **NEW** — словарь активных asyncio.Task |
|
||
| `run_task()` | `def` → `async def` | Запуск через `asyncio.create_task` |
|
||
| `cancel_task()` | `def` → `async def` | Отмена через `task.cancel()` |
|
||
|
||
### EventBus
|
||
|
||
| Attribute | Type | Change |
|
||
|-----------|------|--------|
|
||
| `_queue` | `queue.Queue` → `asyncio.Queue` | Замена sync очереди на async |
|
||
| `publish()` | `def` → `async def` | `put_nowait` → `await put` |
|
||
| `subscribe()` | `def` → `async def` | `get` → `await get` |
|
||
|
||
### LLM Client (unified)
|
||
|
||
| Attribute | Type | Change |
|
||
|-----------|------|--------|
|
||
| `call_openai_compatible()` | `def` → `async def` | Асинхронный HTTP |
|
||
| `_do_http_request()` | `requests.post` + `time.sleep` | `httpx.AsyncClient.post` + `asyncio.sleep` |
|
||
| `timeout` | `int` (180) → `httpx.Timeout(180)` | Настраиваемый таймаут |
|
||
|
||
### Notification Providers
|
||
|
||
| Provider | Method | Change |
|
||
|----------|--------|--------|
|
||
| SMTPProvider | `send()` | `smtplib.SMTP` → `aiosmtplib.SMTP` |
|
||
| TelegramProvider | `send()` | `requests.post` → `httpx.AsyncClient.post` |
|
||
| SlackProvider | `send()` | `requests.post` → `httpx.AsyncClient.post` |
|
||
|
||
## 2. New Entities
|
||
|
||
### SupersetClientRegistry [C:4]
|
||
|
||
Синглтон-реестр долгоживущих асинхронных клиентов по `env_id`:
|
||
|
||
| Attribute | Type | Description |
|
||
|-----------|------|-------------|
|
||
| `_clients` | `dict[str, _ClientSlot]` | env_id → slot с shared ресурсами |
|
||
| `_ClientSlot.client` | `httpx.AsyncClient` | Долгоживущий HTTP-клиент (connection pooling) |
|
||
| `_ClientSlot.semaphore` | `asyncio.Semaphore` | Глобальный per‑env семафор (connection_pool_size) |
|
||
| `_ClientSlot.auth_lock` | `asyncio.Lock` | Защита конкурентного обновления CSRF/auth‑токенов |
|
||
|
||
**Lifecycle**: Клиенты создаются при первом обращении к `env_id`, живут до shutdown. `async with` — только для тестов.
|
||
|
||
### DB Threading Contract
|
||
|
||
SQLAlchemy‑сессии создаются, используются, коммитятся/откатываются и закрываются **внутри одного worker‑потока** (через `asyncio.to_thread(db_executor, ...)`). Никакие ORM‑объекты не пересекают границу потока без явного detach/serialization. DB‑адаптер владеет жизненным циклом сессии.
|
||
|
||
## 3. Configuration Parameters
|
||
|
||
### Environment Config (per Superset instance — `EnvironmentConfig`)
|
||
|
||
```json
|
||
{
|
||
"connection_pool_size": 20,
|
||
"connection_pool_timeout": 30,
|
||
"request_timeout": 30,
|
||
"llm_timeout": 120
|
||
}
|
||
```
|
||
|
||
### App-Level Async Runtime Config (global — отдельная секция конфигурации приложения, не per‑env)
|
||
|
||
```json
|
||
{
|
||
"db_executor_workers": 10,
|
||
"file_executor_workers": 10,
|
||
"git_executor_workers": 5,
|
||
"graceful_shutdown_timeout": 30
|
||
}
|
||
```
|
||
|
||
Эти параметры добавляются в модель `EnvironmentConfig` (Pydantic) и хранятся в БД как часть конфигурации окружения.
|
||
|
||
## 3. Unchanged Entities
|
||
|
||
Следующие сущности **не меняют** свою структуру, только способ взаимодействия с внешними сервисами:
|
||
|
||
| Entity | Type | Notes |
|
||
|--------|------|-------|
|
||
| `TranslationJob` | SQLAlchemy Model | Без изменений |
|
||
| `TranslationPreviewSession` | SQLAlchemy Model | Без изменений |
|
||
| `TranslationPreviewRecord` | SQLAlchemy Model | Без изменений |
|
||
| `TranslationPreviewLanguage` | SQLAlchemy Model | Без изменений |
|
||
| `Dictionary` | SQLAlchemy Model | Без изменений |
|
||
| `LLMProvider` | SQLAlchemy Model | Без изменений |
|
||
| `EnvironmentConfig` | SQLAlchemy Model | Добавлены pool-параметры (см. выше) |
|
||
| Все Pydantic request/response schemas | Pydantic | Без изменений (FR-018) |
|
||
| Все API routes (URL, method, status codes) | FastAPI Router | Без изменений |
|
||
| `PluginBase.execute()` contract | ABC | Уже `async def`, без изменений |
|
||
|
||
## 4. Task State Transitions (Unchanged)
|
||
|
||
```
|
||
PENDING → RUNNING → COMPLETED
|
||
→ FAILED
|
||
→ CANCELLED
|
||
```
|
||
|
||
Переходы состояний не меняются. Меняется только механизм запуска: `ThreadPoolExecutor.submit()` → `asyncio.create_task()`.
|
||
|
||
## 5. WebSocket Message Format (Unchanged)
|
||
|
||
```json
|
||
{
|
||
"type": "log",
|
||
"task_id": "uuid",
|
||
"timestamp": "ISO-8601",
|
||
"level": "INFO|WARNING|ERROR",
|
||
"message": "...",
|
||
"trace_id": "uuid"
|
||
}
|
||
```
|
||
|
||
Формат сообщений WebSocket не меняется. Меняется только транспорт отправки: `queue.Queue` + `call_soon_threadsafe` → `asyncio.Queue` + `await`.
|