docs: архитектурный анализ + History + gitignore (2026-06-27)

This commit is contained in:
“Naeel”
2026-06-27 13:00:18 +04:00
parent 4a21d77f51
commit 82c5c075f1
154 changed files with 4789 additions and 1443 deletions
+100
View File
@@ -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"