docs: архитектурный анализ + History + gitignore (2026-06-27)
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# Ответ Опуса — фоновая классификация при 50+ файлах
|
||||
|
||||
Ответ на `History/opus-classify-async-question.md` от 25.06.2026.
|
||||
|
||||
---
|
||||
|
||||
## Диагноз: что рвётся на самом деле
|
||||
|
||||
**nginx НЕ виноват.** `classify-batch` отдаёт `202` мгновенно. `batch-progress` — короткие независимые запросы, укладываются в 30с. Классификация идёт `classify_worker → api.aillm.ru` напрямую, минуя nginx.
|
||||
|
||||
Реальное узкое место — **симулятор сдаётся на 4-й минуте** (120 итераций × 2с), а 69 файлов при 4 воркерах не успевают: 69 / 4 ≈ 18 волн × 10-15с/LLM-вызов = 3-5 минут > лимита симулятора.
|
||||
|
||||
---
|
||||
|
||||
## Ответы
|
||||
|
||||
### Q1. `subprocess.Popen` для прода — норм?
|
||||
|
||||
Для масштаба сотни-тысячи файлов регулярно `Popen`-на-запрос **неадекватен**:
|
||||
- Нет супервизии: упал worker — никто не узнал (stdout/stderr в DEVNULL)
|
||||
- Нет авто-рестарта
|
||||
- Гонки при параллельных батчах с одним `batch_id`
|
||||
|
||||
**Целевая архитектура:** постоянный worker-сервис под отдельным systemd-юнитом + очередь задач на основе БД.
|
||||
|
||||
Почему БД-очередь, а не Redis:
|
||||
- Work items уже в БД (`documents.classify_status='pending'`)
|
||||
- Устойчиво к рестартам ВМ — resumable
|
||||
- Redis не установлен в проде
|
||||
- Для одной ВМ Celery/RQ — оверкилл
|
||||
|
||||
Дополнительно при тысячах файлов:
|
||||
- retry/backoff на 429/5xx от api.aillm.ru
|
||||
- rate-limit к LLM API
|
||||
- Аккуратное повышение параллелизма (не 4 воркера, а 10-15)
|
||||
|
||||
### Q2. nginx при долгих запросах
|
||||
|
||||
nginx НЕ узкое место. classify-batch=202, batch-progress<30s, классификация идёт worker→LLM напрямую. Реальное узкое: throughput (4 воркера) + лимит симулятора. Прогресс durable в БД, UI не зависит от дедлайна.
|
||||
|
||||
### Q3. Два classify подряд — два Popen
|
||||
|
||||
- **Разные `batch_id`** — безопасно. Каждый процесс работает только над своими документами.
|
||||
- **Один и тот же `batch_id` дважды** — гонка! Оба сделают `reset_classify_status` + повторные LLM-вызовы → двойные траты токенов, неконсистентные счётчики.
|
||||
|
||||
**Решение:** НЕ убивать предыдущий процесс (опасно — может испортить БД). Guard через проверку: если для `batch_id` уже есть running-процесс → вернуть `{"ok": false, "error": "already running"}`. Реализация: либо файл-лок (`/tmp/classify_<batch_id>.lock`), либо запись в БД.
|
||||
|
||||
### Q4. Мониторинг/логирование
|
||||
|
||||
DEVNULL недопустим на масштабе — падения невидимы.
|
||||
|
||||
**Минимум сейчас:** лог-файл per batch `/home/naeel/contracts/logs/classify_<batch_id>.log` (Python logging) вместо DEVNULL.
|
||||
|
||||
**Целевое:** job-таблица в БД:
|
||||
```sql
|
||||
CREATE TABLE classify_jobs (
|
||||
batch_id UUID PRIMARY KEY,
|
||||
started_at TIMESTAMP,
|
||||
finished_at TIMESTAMP,
|
||||
total INT, done INT, failed INT,
|
||||
error_text TEXT,
|
||||
pid INT
|
||||
);
|
||||
```
|
||||
Даёт: честный прогресс, видимость падений, per-doc retry, аудит.
|
||||
|
||||
---
|
||||
|
||||
## Целевая архитектура (будущее)
|
||||
|
||||
```
|
||||
systemd: contracts.service (HTTP)
|
||||
systemd: classify-worker.service (фоновая классификация)
|
||||
|
||||
flow:
|
||||
HTTP → 202 + запись в classify_jobs (status='queued')
|
||||
classify-worker (постоянно):
|
||||
SELECT batch_id FROM classify_jobs WHERE status='queued' LIMIT 1
|
||||
→ status='running'
|
||||
→ classify_batch(batch_id)
|
||||
→ status='done' + метрики
|
||||
→ следующий батч
|
||||
|
||||
Прогресс: documents.classify_status (pending/classified/failed)
|
||||
Поллинг: GET /api/batch-progress?batch=X → count by status
|
||||
```
|
||||
|
||||
Преимущества:
|
||||
- Устойчиво к рестартам (состояние в БД)
|
||||
- Один процесс обрабатывает батчи последовательно — нет гонок
|
||||
- systemd мониторит и рестартует при падении
|
||||
- Логи systemd/journald
|
||||
|
||||
---
|
||||
|
||||
## Минимум сейчас (без переписывания архитектуры)
|
||||
|
||||
1. **Лог-файл вместо DEVNULL:** `stdout=open(log_path, 'w')`
|
||||
2. **Лимит симулятора:** увеличить `MAX_WAIT` до 600с (10 мин) для bulk-тестов
|
||||
3. **Guard от двойного classify:** файл-лок `/tmp/classify_<batch_id>.lock` — если существует, вернуть "already running"
|
||||
Reference in New Issue
Block a user